Python 教程 10:第一个实用程序

“纸上得来终觉浅,绝知此事要躬行。”

经过前面 9 课的学习,我们已经掌握了 Python 的基础知识。今天,让我们把这些知识串起来,开发一个真正实用的程序:批量文件重命名工具

1. 项目需求

开发一个命令行工具,能够:

  1. 批量重命名文件:支持添加前缀、后缀、替换文本
  2. 过滤文件:支持按扩展名、文件名模式过滤
  3. 预览模式:先预览修改,确认后再执行
  4. 撤销功能:记录操作,支持撤销

这个工具很实用,能解决日常工作中的真实问题。

2. 项目结构

file_renamer/
├── file_renamer.py    # 主程序
├── renamer.py         # 核心重命名逻辑
├── utils.py           # 工具函数
└── history.json       # 操作历史记录

3. 核心功能实现

3.1 列出目录中的文件

 1# utils.py
 2import os
 3
 4def list_files(directory, extension=None, pattern=None):
 5    """
 6    列出目录中的文件
 7
 8    Args:
 9        directory: 目标目录
10        extension: 文件扩展名过滤(如'.txt')
11        pattern: 文件名模式(简单的包含匹配)
12
13    Returns:
14        文件路径列表
15    """
16    files = []
17
18    for filename in os.listdir(directory):
19        filepath = os.path.join(directory, filename)
20
21        # 只处理文件,忽略目录
22        if not os.path.isfile(filepath):
23            continue
24
25        # 扩展名过滤
26        if extension and not filename.endswith(extension):
27            continue
28
29        # 文件名模式过滤
30        if pattern and pattern not in filename:
31            continue
32
33        files.append(filepath)
34
35    return files

3.2 重命名逻辑

 1# renamer.py
 2import os
 3import re
 4
 5class FileRenamer:
 6    """文件重命名器"""
 7
 8    def __init__(self, directory):
 9        self.directory = directory
10        self.changes = []  # 记录修改
11
12    def add_prefix(self, files, prefix):
13        """添加前缀"""
14        for filepath in files:
15            dirname = os.path.dirname(filepath)
16            filename = os.path.basename(filepath)
17            new_name = prefix + filename
18            new_path = os.path.join(dirname, new_name)
19            self.changes.append((filepath, new_path))
20
21    def add_suffix(self, files, suffix):
22        """添加后缀(在扩展名前)"""
23        for filepath in files:
24            dirname = os.path.dirname(filepath)
25            filename = os.path.basename(filepath)
26            name, ext = os.path.splitext(filename)
27            new_name = name + suffix + ext
28            new_path = os.path.join(dirname, new_name)
29            self.changes.append((filepath, new_path))
30
31    def replace_text(self, files, old_text, new_text):
32        """替换文件名中的文本"""
33        for filepath in files:
34            dirname = os.path.dirname(filepath)
35            filename = os.path.basename(filepath)
36            new_name = filename.replace(old_text, new_text)
37            new_path = os.path.join(dirname, new_name)
38            if filepath != new_path:  # 只记录有变化的
39                self.changes.append((filepath, new_path))
40
41    def preview(self):
42        """预览修改"""
43        if not self.changes:
44            print("没有要修改的文件")
45            return
46
47        print(f"\n将要进行 {len(self.changes)} 项修改:")
48        print("-" * 60)
49        for i, (old, new) in enumerate(self.changes, 1):
50            old_name = os.path.basename(old)
51            new_name = os.path.basename(new)
52            print(f"{i}. {old_name} -> {new_name}")
53        print("-" * 60)
54
55    def execute(self):
56        """执行重命名"""
57        if not self.changes:
58            print("没有要执行的操作")
59            return
60
61        success_count = 0
62        for old_path, new_path in self.changes:
63            try:
64                os.rename(old_path, new_path)
65                success_count += 1
66            except Exception as e:
67                print(f"错误:{old_path} -> {e}")
68
69        print(f"\n成功重命名 {success_count}/{len(self.changes)} 个文件")
70
71        # 保存操作历史
72        self.save_history()
73
74    def save_history(self):
75        """保存操作历史(简化版)"""
76        import json
77        from datetime import datetime
78
79        history_file = "history.json"
80
81        # 读取现有历史
82        history = []
83        if os.path.exists(history_file):
84            with open(history_file, 'r', encoding='utf-8') as f:
85                history = json.load(f)
86
87        # 添加新记录
88        history.append({
89            'time': datetime.now().isoformat(),
90            'changes': [(old, new) for old, new in self.changes]
91        })
92
93        # 保存
94        with open(history_file, 'w', encoding='utf-8') as f:
95            json.dump(history, f, indent=2, ensure_ascii=False)

3.3 主程序

 1# file_renamer.py
 2#!/usr/bin/env python3
 3# -*- coding: utf-8 -*-
 4
 5"""
 6文件批量重命名工具
 7
 8用法:
 9    python file_renamer.py
10"""
11
12import os
13from renamer import FileRenamer
14from utils import list_files
15
16def main():
17    print("=" * 60)
18    print("文件批量重命名工具")
19    print("=" * 60)
20
21    # 获取目录
22    directory = input("\n请输入目录路径(留空使用当前目录):").strip()
23    if not directory:
24        directory = "."
25
26    if not os.path.exists(directory):
27        print(f"错误:目录 '{directory}' 不存在")
28        return
29
30    # 获取文件过滤条件
31    extension = input("文件扩展名过滤(如.txt,留空跳过):").strip()
32    if not extension:
33        extension = None
34
35    # 列出文件
36    files = list_files(directory, extension)
37
38    if not files:
39        print("没有找到符合条件的文件")
40        return
41
42    print(f"\n找到 {len(files)} 个文件")
43
44    # 创建重命名器
45    renamer = FileRenamer(directory)
46
47    # 操作菜单
48    while True:
49        print("\n请选择操作:")
50        print("1. 添加前缀")
51        print("2. 添加后缀")
52        print("3. 替换文本")
53        print("4. 预览修改")
54        print("5. 执行重命名")
55        print("0. 退出")
56
57        choice = input("\n请输入选择:").strip()
58
59        if choice == "1":
60            prefix = input("请输入前缀:")
61            renamer.add_prefix(files, prefix)
62            print("✓ 已添加前缀规则")
63
64        elif choice == "2":
65            suffix = input("请输入后缀:")
66            renamer.add_suffix(files, suffix)
67            print("✓ 已添加后缀规则")
68
69        elif choice == "3":
70            old_text = input("请输入要替换的文本:")
71            new_text = input("请输入新文本:")
72            renamer.replace_text(files, old_text, new_text)
73            print("✓ 已添加替换规则")
74
75        elif choice == "4":
76            renamer.preview()
77
78        elif choice == "5":
79            renamer.preview()
80            confirm = input("\n确认执行?(y/N):").strip().lower()
81            if confirm == 'y':
82                renamer.execute()
83                break
84            else:
85                print("已取消")
86
87        elif choice == "0":
88            print("再见!")
89            break
90
91        else:
92            print("无效的选择")
93
94if __name__ == "__main__":
95    main()

4. 使用示例

场景 1:照片重命名

假设有一批照片:

IMG_001.jpg
IMG_002.jpg
IMG_003.jpg

使用工具添加前缀"2024vacation",变成:

2024_vacation_IMG_001.jpg
2024_vacation_IMG_002.jpg
2024_vacation_IMG_003.jpg

场景 2:文档整理

文档文件名:

report_draft.doc
report_final.docx
report_review.docx

替换"report"为"年度总结":

年度总结_draft.doc
年度总结_final.docx
年度总结_review.docx

5. 改进方向

这个基础版本还可以继续改进:

  1. 正则表达式支持:更强大的匹配和替换
  2. 序号重命名:按序号重命名文件
  3. 日期戳支持:在文件名中添加日期
  4. 递归目录:处理子目录中的文件
  5. GUI 界面:使用 tkinter 创建图形界面
  6. 撤销功能完善:真正实现撤销

6. 知识点回顾

这个项目用到了我们学过的:

  • 变量和数据类型:字符串、列表、字典
  • 控制流程:if、for、while
  • 函数:模块化设计
  • 字符串操作:替换、分割、拼接
  • 文件操作:os 模块、文件读写
  • 异常处理:try-except
  • 编码规范:遵循 PEP 8

7. 小结

今天我们完成了第一个实用程序!

收获

  • 理解了如何将知识点串联起来
  • 学会了模块化设计
  • 体验了真实的开发流程:需求 → 设计 → 实现 → 测试

这只是开始。接下来的课程会学习更多高级特性,让你能开发更复杂的应用。


练习题

  1. 为程序添加序号重命名功能(如 001、002、003)
  2. 实现"撤销上一次操作"功能
  3. 添加进度条显示(使用 tqdm 库)

思考题

如果要开发一个图形界面版本,你会用什么库?如何设计界面?


本文代码示例


相关阅读