尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

大话批量重命名工具:Python实现详解

大话批量重命名工具:Python实现详解
📅 发布时间:2026/7/12 0:42:52

各位看官,今天咱们要聊的这个项目,可不是什么高深莫测的人工智能,也不是什么复杂难懂的分布式系统。它就是一个普普通通的批量重命名工具。但别小看它,麻雀虽小,五脏俱全。咱们今天就来好好唠唠,这个工具是怎么用Python一点点搭建起来的。


第一章:需求分析——从"手动改文件名"说起

话说在很久很久以前,有一个程序员,他有一堆电子书,文件名乱七八糟的:

【精品】三体全集_精校版.epub 【转载】百年孤独_[哥伦比亚]加西亚·马尔克斯.pdf 【高清】红楼梦_曹雪芹.epub

他想把这些文件名整理得干净一点,变成:

三体全集.epub 百年孤独.pdf 红楼梦.epub

于是他开始手动一个个改,改到第三个文件的时候,他崩溃了——文件太多了!

需求就这样产生了:

  • 批量处理文件重命名

  • 支持移除前缀、后缀、中间任意字符串

  • 支持替换字符串

  • 支持修改扩展名

  • 支持添加前缀、后缀

这就好比你有一堆乱码的书籍,你需要一个图书管理员来帮你整理。


第二章:项目架构——麻雀虽小,五脏俱全

咱们先来看看这个项目的目录结构:

ChangeFileName/ ├── src/ │ ├── __init__.py │ ├── main.py # 主程序入口,核心逻辑 │ ├── cli.py # 命令行参数解析 │ ├── gui.py # Tkinter GUI界面 │ ├── qt_gui/ # Qt5 GUI界面 │ │ ├── __init__.py │ │ └── main_window.py │ ├── file_scanner.py # 文件扫描模块 │ ├── renamer.py # 重命名核心算法 │ └── history.py # 历史记录管理 ├── tests/ # 单元测试 ├── dist/ # 打包产物 └── README.md # 使用说明

这个架构就像一个小工厂:

  • file_scanner.py是采购员,负责去各个目录"采购"文件

  • renamer.py是加工车间,负责对文件名进行各种"加工"

  • history.py是仓库,负责保存历史记录,方便"退货"

  • main.py是厂长,负责协调各个部门

  • gui.py和qt_gui/是销售部,负责和用户打交道


第三章:文件扫描——目录树的深度遍历

咱们先来聊聊文件扫描这个模块。这个模块的核心任务,就是遍历指定目录,找出符合条件的文件。

3.1 单线程扫描——一个人的长征

最开始,咱用的是单线程扫描:

def scan_directory(directory: str, extensions: List[str], depth: int = 1) -> List[Tuple[Path, str]]: result = [] dir_path = Path(directory) if depth == 1: # 只扫当前目录,就像在自家院子里找东西 for file_path in dir_path.iterdir(): if file_path.is_file(): # 检查扩展名是否匹配 file_ext = file_path.suffix.lower().strip('.') if file_ext in ext_set: result.append((file_path, file_path.name)) elif depth == 0: # 无限深度扫描,就像孙悟空钻进铁扇公主的肚子里 for file_path in dir_path.rglob("*"): if file_path.is_file(): file_ext = file_path.suffix.lower().strip('.') if file_ext in ext_set: result.append((file_path, file_path.name)) else: # 指定深度扫描,就像挖井,挖到指定深度就停 for file_path in dir_path.rglob("*"): if file_path.is_file(): rel_path = file_path.relative_to(dir_path) if len(rel_path.parts) <= depth: file_ext = file_path.suffix.lower().strip('.') if file_ext in ext_set: result.append((file_path, file_path.name)) return result

这段代码的逻辑很简单:

  • depth=1:只扫描当前目录,用iterdir()

  • depth=0:无限深度扫描,用rglob("*")

  • depth=n:扫描指定深度,用rglob("*")然后判断相对路径的层级

3.2 多线程扫描——众人拾柴火焰高

后来发现单线程扫描太慢了,尤其是文件多的时候。于是咱引入了多线程:

def scan_directory(directory: str, extensions: List[str], depth: int = 1, threads: int = 1) -> List[Tuple[Path, str]]: result = [] dir_path = Path(directory) ext_set = {ext.lower().strip('.') for ext in extensions} if threads <= 1: # 单线程,老规矩 return _scan_single_directory(dir_path, ext_set, depth, dir_path) # 多线程扫描,就像派出多个小分队 from concurrent.futures import ThreadPoolExecutor subdirs = [p for p in dir_path.iterdir() if p.is_dir()] with ThreadPoolExecutor(max_workers=threads) as executor: futures = [] for subdir in subdirs: futures.append(executor.submit( _scan_single_directory, subdir, ext_set, depth - 1, dir_path )) for future in futures: result.extend(future.result()) # 别忘了扫当前目录 result.extend(_scan_single_directory(dir_path, ext_set, 1, dir_path)) return result

多线程扫描的思路是:

  1. 把当前目录下的子目录分配给不同的线程

  2. 每个线程负责扫描自己的子目录

  3. 最后合并所有结果

这里用到了concurrent.futures.ThreadPoolExecutor,这是Python标准库中非常好用的线程池实现。


第四章:重命名算法——文件名的七十二变

重命名算法是这个工具的核心,就像孙悟空的七十二变,能把文件名变成各种样子。

4.1 核心数据结构

咱们先看看重命名过程中用到的数据结构:

class RenameResult: """重命名结果""" def __init__(self): self.status = ResultStatus.NO_MATCH # 状态 self.changes = [] # 变更列表 [(old_name, new_name), ...] self.scanned_files = [] # 扫描到的文件列表 self.skipped_files = [] # 跳过的文件(无效文件名) self.conflict_files = [] # 冲突文件(新文件名已存在) self.error_message = "" # 错误信息 self.success_count = 0 # 成功数量 self.fail_count = 0 # 失败数量

这个数据结构就像一个账本,记录了重命名过程中的所有情况。

4.2 文件名处理流程

文件名的处理流程就像一条流水线:

原文件名 → 移除前缀 → 移除后缀 → 移除任意字符串 → 替换字符串 → 修改扩展名 → 添加前缀 → 添加后缀 → 新文件名

咱们来看看具体实现:

def generate_new_filename( filename: str, prefix: Optional[str] = None, suffix: Optional[str] = None, remove: Optional[List[str]] = None, replace: Optional[List[Tuple[str, str]]] = None, replace_ext: Optional[Tuple[str, str]] = None, add_prefix: Optional[str] = None, add_suffix: Optional[str] = None, use_regex: bool = False, ignore_case: bool = False ) -> str: """生成新文件名""" name, ext = os.path.splitext(filename) # 移除前缀 if prefix: name = remove_prefix(name, prefix, use_regex, ignore_case) # 移除后缀 if suffix: name = remove_suffix(name, suffix, use_regex, ignore_case) # 移除任意字符串 if remove: for s in remove: name = remove_string(name, s, use_regex, ignore_case) # 替换字符串 if replace: for old_str, new_str in replace: name = replace_string(name, old_str, new_str, use_regex, ignore_case) # 修改扩展名 if replace_ext: old_ext, new_ext = replace_ext if old_ext is None or ext.lower() == f'.{old_ext.lower()}': ext = f'.{new_ext}' # 添加前缀 if add_prefix: name = add_prefix + name # 添加后缀 if add_suffix: name = name + add_suffix return name + ext

这段代码的逻辑很清晰,就像按顺序执行一系列操作。

4.3 字符串处理函数

咱们来看看几个关键的字符串处理函数:

def remove_prefix(name: str, prefix: str, use_regex: bool = False, ignore_case: bool = False) -> str: """移除前缀""" if use_regex: flags = re.IGNORECASE if ignore_case else 0 return re.sub(r'^' + re.escape(prefix), '', name, flags=flags) else: if ignore_case: if name.lower().startswith(prefix.lower()): return name[len(prefix):] else: if name.startswith(prefix): return name[len(prefix):] return name

这个函数支持两种模式:

  • 普通模式:直接判断是否以指定前缀开头

  • 正则模式:使用正则表达式匹配

def replace_string(name: str, old_str: str, new_str: str, use_regex: bool = False, ignore_case: bool = False) -> str: """替换字符串""" if use_regex: flags = re.IGNORECASE if ignore_case else 0 return re.sub(old_str, new_str, name, flags=flags) else: if ignore_case: import re return re.sub(re.escape(old_str), new_str, name, flags=re.IGNORECASE) else: return name.replace(old_str, new_str)

替换函数也类似,支持普通替换和正则替换。


第五章:历史记录——时光倒流的秘密

重命名操作是不可逆的,万一改错了怎么办?别担心,咱有历史记录功能。

5.1 历史记录的数据结构
class HistoryEntry: """历史记录条目""" def __init__(self): self.timestamp = 0 # 时间戳 self.changes = [] # 变更列表 def to_dict(self): return { 'timestamp': self.timestamp, 'changes': self.changes } @classmethod def from_dict(cls, data): entry = cls() entry.timestamp = data['timestamp'] entry.changes = data['changes'] return entry

历史记录就像一本日记,记录了每次重命名操作。

5.2 历史记录的存储

历史记录存储在一个JSON文件里,放在目标目录的.rename_history文件夹中:

def save_history(directory: str, changes: List[Tuple[str, str]]): """保存历史记录""" history_dir = os.path.join(directory, '.rename_history') os.makedirs(history_dir, exist_ok=True) entry = HistoryEntry() entry.timestamp = int(time.time()) entry.changes = changes history_file = os.path.join(history_dir, f'{entry.timestamp}.json') with open(history_file, 'w', encoding='utf-8') as f: json.dump(entry.to_dict(), f, ensure_ascii=False)

每次执行重命名操作后,都会保存一条历史记录。

5.3 撤销操作——时光倒流

撤销操作就是读取最近的一条历史记录,然后反向执行重命名:

def undo_rename(directory: str) -> RenameResult: """撤销最近一次重命名操作""" result = RenameResult() if not has_history(directory): result.error_message = "没有可撤销的历史记录" return result entry = undo_last(directory) if entry is None: result.error_message = "没有可撤销的历史记录" return result # 反向执行重命名 for old_name, new_name in entry.changes: file_path = os.path.join(directory, old_name) if os.path.exists(file_path): try: rename_file(Path(file_path), new_name) result.success_count += 1 except Exception as e: result.fail_count += 1 else: result.fail_count += 1 return result

这里的关键是,历史记录中保存的是(原文件名, 新文件名),撤销时就把新文件名改回原文件名。


第六章:GUI界面——用户与程序的桥梁

一个好的工具,不仅要有强大的功能,还要有友好的界面。

6.1 Tkinter版本——朴实无华

Tkinter是Python自带的GUI库,就像一个朴素的农家小院,虽然不华丽,但很实用。

class RenameApp: def __init__(self, root): self.root = root self.root.title("批量重命名工具") # 创建各种控件 self.directory = tk.StringVar() self.extensions = tk.StringVar(value="epub pdf") # ... self._build_ui() def _build_ui(self): # 构建界面布局 main_frame = ttk.Frame(self.root, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) notebook = ttk.Notebook(main_frame) # 添加配置、文件列表、预览三个选项卡

Tkinter的界面很简单,用Frame、Label、Entry、Button等控件就能搭建起来。

6.2 Qt5版本——华丽转身

后来咱用PyQt5重写了GUI,就像把农家小院改成了别墅:

class RenameQtApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("批量重命名工具") self.setGeometry(100, 100, 1000, 750) self.init_ui() def init_ui(self): central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) self.tab_widget = QTabWidget() main_layout.addWidget(self.tab_widget) self.build_config_panel() self.build_file_list_panel() self.build_preview_panel()

Qt5的界面更加现代化,支持更多的样式和功能。

6.3 异步文件扫描——界面不卡顿的秘密

在GUI版本中,文件扫描是一个耗时操作,如果在主线程中执行,会导致界面卡顿。所以咱用了异步处理:

Tkinter版本:

def _refresh_file_list(self): # 在后台线程中执行扫描 def scan_worker(): files = scan_directory(directory, self._get_extensions(), self._get_depth(), self._get_threads()) self.root.after(0, self._update_file_list, files) thread = threading.Thread(target=scan_worker) thread.daemon = True thread.start()

Qt5版本:

class ScanWorker(QThread): finished = pyqtSignal(list) def run(self): files = scan_directory(self.directory, self.extensions, self.depth, self.threads) self.finished.emit(files) ​ def refresh_file_list(self): self.scan_worker = ScanWorker(directory, extensions, depth, threads) self.scan_worker.finished.connect(self.update_file_list) self.scan_worker.start()

两种版本都用了后台线程来执行扫描操作,避免阻塞主线程。


第七章:命令行接口——程序员的最爱

除了GUI,咱还提供了命令行接口,方便程序员使用:

def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace: parser = argparse.ArgumentParser( prog='batch-rename', description='批量重命名工具' ) parser.add_argument('directory', help='目标目录路径') parser.add_argument('-e', '--extensions', nargs='+', default=['epub', 'pdf']) parser.add_argument('-p', '--prefix', help='要移除的前缀') parser.add_argument('-s', '--suffix', help='要移除的后缀') parser.add_argument('-r', '--remove', action='append', help='要移除的字符串') parser.add_argument('--replace', action='append', help='替换字符串,格式 OLD:NEW') parser.add_argument('--replace-ext', metavar='OLD:NEW', help='替换扩展名') parser.add_argument('--add-prefix', help='添加前缀') parser.add_argument('--add-suffix', help='添加后缀') parser.add_argument('--regex', action='store_true', help='使用正则表达式') parser.add_argument('--ignore-case', action='store_true', help='忽略大小写') parser.add_argument('--depth', type=int, default=1, help='遍历深度') parser.add_argument('--threads', type=int, default=1, help='线程数') parser.add_argument('-d', '--dry-run', action='store_true', help='预览模式') parser.add_argument('-f', '--force', action='store_true', help='强制执行') parser.add_argument('-u', '--undo', action='store_true', help='撤销') parser.add_argument('--gui', choices=['tk', 'qt'], help='启动GUI') return parser.parse_args(args)

使用示例:

# 移除前缀【精品】 python -m src.main /path/to/dir -p "【精品】" ​ # 替换字符串 python -m src.main /path/to/dir --replace "【转载】:" ​ # 修改扩展名 python -m src.main /path/to/dir --replace-ext "epub:mobi" ​ # 预览模式 python -m src.main /path/to/dir -p "【精品】" -d ​ # 无限深度扫描 python -m src.main /path/to/dir --depth 0 --threads 4

第八章:打包发布——从代码到可执行文件

最后,咱用PyInstaller把项目打包成了exe文件,方便用户使用:

# 打包GUI版本 pyinstaller --onefile --windowed --name "BatchRenameTool" src/main.py ​ # 打包CLI版本 pyinstaller --onefile --name "BatchRenameTool_CLI" src/main.py

打包过程中遇到了一些坑,比如缺少platformdirs依赖、Tcl/Tk DLL缺失等,但都一一解决了。


第九章:总结——一个工具的诞生

回顾这个项目的开发过程,咱用到了:

技术点实现方式
文件扫描Pathlib + 递归遍历 + 多线程
字符串处理正则表达式 + 字符串方法
历史记录JSON文件存储
GUI界面Tkinter + PyQt5
命令行接口argparse
打包发布PyInstaller

这个工具虽然简单,但它体现了软件开发的基本流程:

  1. 需求分析:明确用户需求

  2. 架构设计:设计合理的模块划分

  3. 核心实现:实现核心功能

  4. 界面开发:提供友好的用户界面

  5. 测试验证:确保功能正确

  6. 打包发布:方便用户使用

就像《大话数据结构》里说的,数据结构和算法是编程的基本功。同样,良好的代码结构和清晰的逻辑也是开发一个好工具的关键。

希望这篇文章能让你对这个项目有更深入的了解,也希望能给你带来一些启发。如果你有什么想法或建议,欢迎留言讨论!


后记:这个项目还在不断进化中,说不定哪天就会加入更多神奇的功能。如果你想了解最新动态,欢迎关注项目的GitHub仓库。

相关新闻

  • 2026年7月最新盐城卡地亚官方售后热线及客户服务网点地址 - 卡地亚服务中心
  • 2026年复合料调味瓶工厂业内优质合作厂家 - 热点品牌推荐
  • 2026深圳搬家全维度实测攻略|各区痛点拆解+同城搬家公认TOP5正规品牌甄选+透明收费避坑全指南 - 从来都是英雄出少年

最新新闻

  • 手机控电脑怎么选?ToDesk虚拟鼠标完胜向日葵AnyDesk,实测告诉你谁才是移动生产力神器
  • 同样的钱,Minimax 能做的事怎么越变越少了?
  • AI如何自动识别企业资质废标风险?智能评审项目实践
  • UE5.4 VR一体机APK打包全链路实践:从环境配置到性能优化
  • L3自动驾驶:人机责任边界的工程重构与落地实践
  • AI编程与自动驾驶的范式共性:从代码生成到可验证契约

日新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

周新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号