用 PyQt5 做一个「批量目录重命名」工具,并打包成带图标的 EXE

目录

    • [用 PyQt5 做一个「批量目录重命名」工具,并打包成带图标的 EXE](#用 PyQt5 做一个「批量目录重命名」工具,并打包成带图标的 EXE)
    • 一、功能设计:从「单操作」到「规则流水线」
    • [二、界面布局:一个小而美的 PyQt5 窗口](#二、界面布局:一个小而美的 PyQt5 窗口)
      • [1. 目标目录区域](#1. 目标目录区域)
      • [2. 规则配置区域:编辑 + 列表](#2. 规则配置区域:编辑 + 列表)
      • [3. 预览 / 执行区域](#3. 预览 / 执行区域)
    • 三、规则流水线:如何叠加多个操作
      • [1. 规则的数据结构](#1. 规则的数据结构)
      • [2. 规则的执行:apply_operations](#2. 规则的执行:apply_operations)
    • 四、安全性:冲突检测与两阶段重命名
      • [1. 合法性和重复的预览检查](#1. 合法性和重复的预览检查)
      • [2. 两阶段重命名:避免互相覆盖](#2. 两阶段重命名:避免互相覆盖)
    • [五、让界面更好看一点:简单的 PyQt5 美化](#五、让界面更好看一点:简单的 PyQt5 美化)
    • [六、使用 PyInstaller 打包成单文件 EXE](#六、使用 PyInstaller 打包成单文件 EXE)
      • [1. 把 PNG 转成 ICO(用于 EXE 图标)](#1. 把 PNG 转成 ICO(用于 EXE 图标))
      • [2. 打包命令](#2. 打包命令)
    • 七、总结与可扩展方向

专栏导读

🌸 欢迎来到Python办公自动化专栏---Python处理办公问题,解放您的双手
🏳️‍🌈 个人博客主页:请点击------> 个人的博客主页 求收藏
🏳️‍🌈 Github主页:请点击------> Github主页 求Star⭐
🏳️‍🌈 知乎主页:请点击------> 知乎主页 求关注
🏳️‍🌈 CSDN博客主页:请点击------> CSDN的博客主页 求关注
👍 该系列文章专栏:请点击------>Python办公自动化专栏 求订阅
🕷 此外还有爬虫专栏:请点击------>Python爬虫基础专栏 求订阅
📕 此外还有python基础专栏:请点击------>Python基础学习专栏 求订阅
文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
❤️ 欢迎各位佬关注! ❤️

点击 投票 获取Python办公自动化高清书籍
点击 投票 获取Python办公自动化高清书籍
点击 投票 获取Python办公自动化高清书籍
(Excel Python:飞速搞定数据分析与处理 (【瑞士】费利克斯·朱姆斯坦) )

用 PyQt5 做一个「批量目录重命名」工具,并打包成带图标的 EXE

目录整理是一个非常常见但又枯燥的工作:

比如下载了一堆带前缀/后缀的文件夹、备份目录前面都想加上日期、把目录名中的空格统一改成下划线......手工一个个改,很容易改到崩溃。

这篇文章记录我用 PyQt5 做了一个图形界面的小工具:

支持对某个目录下所有子目录名进行 批量重命名,并且可以:

  • 前面添加(前缀)
  • 后面添加(后缀)
  • 替换某个子串
  • 删除某个子串
    更重要的是:这些操作可以 叠加使用,按顺序从上到下执行。

最后再用 PyInstaller 打包成带自定义图标的单文件 renamedir.exe,方便在任何 Windows 上直接双击使用。

一、功能设计:从「单操作」到「规则流水线」

一开始的需求很简单:

选定一个目录,然后对它下面的一级子目录做统一的「前面添加 / 后面添加 / 替换 / 删除」。

这个版本的界面大概是这样:

  • 一个「目标目录」选择框
  • 一个「操作类型」下拉框(前面添加 / 后面添加 / 替换 / 删除)
  • 两个参数输入框(比如「要替换」「替换为」)
  • 一个「预览」按钮
  • 一个下方表格,显示「原目录名」「新目录名」「状态」
  • 一个「执行重命名」按钮

后来实际使用中,很快发现一个限制:

实际需求往往不是「只做一个操作」,而是类似:

  1. 先把目录名里的 test 都删掉
  2. 再把空格替换成 _
  3. 最后前面加上日期前缀,比如 2026_

如果每次只能做一个操作,就要手动来回切换参数、预览、执行,非常不方便。

于是把「单次操作」升级成了 可叠加的规则列表:像流水线一样依次应用。

设计改动:

  • 之前的「操作类型 + 参数」区域变成「规则编辑器」
  • 下面新增一个「规则列表表格」
  • 支持:
    • 点击「添加规则」把当前操作加入列表
    • 规则上移 / 下移调整顺序
    • 删除单条规则
    • 一键清空全部规则
  • 预览时,会对每个目录名按规则列表的顺序依次执行

这样就可以放心地配置「先删、再替换、再加前缀/后缀」等组合操作了。

二、界面布局:一个小而美的 PyQt5 窗口

整个界面由三个部分组成:

  1. 顶部:目标目录区域
  2. 中间:规则配置区域
  3. 底部:预览和执行

1. 目标目录区域

使用 QGroupBox + QHBoxLayout

  • 标签:路径
  • 输入框:self.dir_edit
  • 浏览按钮:浏览...,通过 QFileDialog.getExistingDirectory 让用户选择目录
python 复制代码
dir_group = QGroupBox("目标目录")
dir_layout = QHBoxLayout()
dir_label = QLabel("路径")
self.dir_edit = QLineEdit()
browse_btn = QPushButton("浏览...")
browse_btn.clicked.connect(self.browse_dir)
dir_layout.addWidget(dir_label)
dir_layout.addWidget(self.dir_edit, 1)
dir_layout.addWidget(browse_btn)
dir_group.setLayout(dir_layout)
main_layout.addWidget(dir_group)

browse_dir 很简单,从对话框拿到目录再写入输入框:

python 复制代码
def browse_dir(self):
    directory = QFileDialog.getExistingDirectory(self, "选择目标目录")
    if directory:
        self.dir_edit.setText(directory)

2. 规则配置区域:编辑 + 列表

规则区域用了一个大的 QGroupBox("规则设置"),里面再嵌两个部分:

  • 上半:规则编辑器(操作类型 + 参数1 + 参数2 + 添加规则按钮)
  • 下半:当前规则列表(表格)+ 规则操作按钮

规则编辑器部分:

python 复制代码
builder_form = QFormLayout()

op_label = QLabel("操作类型")
self.op_combo = QComboBox()
self.op_combo.addItem("前面添加", "prefix")
self.op_combo.addItem("后面添加", "suffix")
self.op_combo.addItem("替换", "replace")
self.op_combo.addItem("删除字符", "delete")

self.param1_label = QLabel("参数1")
self.param1_edit = QLineEdit()
self.param2_label = QLabel("参数2")
self.param2_edit = QLineEdit()

builder_form.addRow(op_label, self.op_combo)
builder_form.addRow(self.param1_label, self.param1_edit)
builder_form.addRow(self.param2_label, self.param2_edit)

add_row = QHBoxLayout()
add_row.addStretch()
self.add_op_btn = QPushButton("添加规则")
add_row.addWidget(self.add_op_btn)
builder_form.addRow(QLabel(""), add_row)

规则列表表格:

python 复制代码
self.ops_table = QTableWidget()
self.ops_table.setColumnCount(4)
self.ops_table.setHorizontalHeaderLabels(["顺序", "类型", "参数1", "参数2"])
self.ops_table.setAlternatingRowColors(True)
self.ops_table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.ops_table.setSelectionMode(QAbstractItemView.SingleSelection)
self.ops_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.ops_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
self.ops_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeToContents)
self.ops_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
self.ops_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
self.ops_table.verticalHeader().setVisible(False)
self.ops_table.setShowGrid(False)

表格下面是几个操作按钮:

  • 上移
  • 下移
  • 删除
  • 清空

3. 预览 / 执行区域

两个主按钮:

python 复制代码
btn_layout = QHBoxLayout()
self.preview_btn = QPushButton("预览")
self.apply_btn = QPushButton("执行重命名")
btn_layout.addStretch()
btn_layout.addWidget(self.preview_btn)
btn_layout.addWidget(self.apply_btn)
main_layout.addLayout(btn_layout)

下方结果表格:

python 复制代码
self.table = QTableWidget()
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels(["原目录名", "新目录名", "状态"])
self.table.setAlternatingRowColors(True)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
self.table.verticalHeader().setVisible(False)
self.table.setShowGrid(False)

三、规则流水线:如何叠加多个操作

核心点是让规则可叠加,并按顺序执行。

1. 规则的数据结构

在窗口类里维护一个列表:

python 复制代码
self.operations = []

每条规则是一个字典:

python 复制代码
{"mode": "prefix", "p1": "2026_", "p2": ""}
{"mode": "replace", "p1": " ", "p2": "_"}
{"mode": "delete", "p1": "test", "p2": ""}

添加规则时,把当前下拉框和参数输入框的值读出来,做一些校验后塞进 self.operations

python 复制代码
def add_operation(self):
    mode = self.op_combo.currentData()
    p1 = self.param1_edit.text()
    p2 = self.param2_edit.text()

    if mode == "replace" and p1 == "":
        QMessageBox.warning(self, "提示", ""要替换"不能为空")
        return
    if mode in ("prefix", "suffix", "delete") and p1 == "":
        QMessageBox.warning(self, "提示", "参数不能为空")
        return

    self.operations.append({"mode": mode, "p1": p1, "p2": p2})
    self.refresh_ops_table(select_index=len(self.operations) - 1)
    self.param1_edit.setText("")
    self.param2_edit.setText("")

refresh_ops_tableself.operations 同步到规则表格里:

python 复制代码
def refresh_ops_table(self, select_index=None):
    self.ops_table.setRowCount(0)
    for i, op in enumerate(self.operations):
        row = self.ops_table.rowCount()
        self.ops_table.insertRow(row)
        self.ops_table.setItem(row, 0, QTableWidgetItem(str(i + 1)))
        self.ops_table.setItem(row, 1, QTableWidgetItem(self.mode_to_text(op["mode"])))
        self.ops_table.setItem(row, 2, QTableWidgetItem(op.get("p1", "")))
        self.ops_table.setItem(row, 3, QTableWidgetItem(op.get("p2", "")))

上移 / 下移 / 删除 / 清空其实就是对 self.operations 做 list 操作,然后调用 refresh_ops_table

2. 规则的执行:apply_operations

给某个目录名应用规则流水线:

python 复制代码
def apply_operations(self, name):
    new_name = name
    for op in self.operations:
        new_name = self.get_new_name(op["mode"], new_name, op.get("p1", ""), op.get("p2", ""))
    return new_name

get_new_name 处理具体的四种模式:

python 复制代码
def get_new_name(self, mode, name, p1, p2):
    if mode == "prefix":
        if not p1:
            return name
        return p1 + name
    if mode == "suffix":
        if not p1:
            return name
        return name + p1
    if mode == "replace":
        if not p1:
            return name
        return name.replace(p1, p2)
    if mode == "delete":
        if not p1:
            return name
        return name.replace(p1, "")
    return name

预览时,就对目录下的每个子目录名调用一次 apply_operations,得到最终的新名字。

四、安全性:冲突检测与两阶段重命名

批量改名一定要小心两件事:

  1. 目标名是否合法(Windows 目录名不能包含 < > : " / \ | ? * 等)
  2. 多个目录是否会改成同一个名字

再进阶一点:有可能存在「互换名字」的情况,比如:

  • 原来有 AB
  • 规则是把 A 改成 B,把 B 改成 A

如果直接 os.rename("A", "B"),后面再改 B -> A 会失败,因为 B 已经存在或者冲突。

1. 合法性和重复的预览检查

预览阶段,对每个目录算完新名字后,会先统计一遍数量:

python 复制代码
new_name_counts = {}
computed = []

for name in dir_names:
    new_name = self.apply_operations(name)
    computed.append((name, new_name))
    new_name_counts[new_name] = new_name_counts.get(new_name, 0) + 1

然后再逐条填到表格里,同时判断:

python 复制代码
if new_name == name:
    continue
if not self.is_valid_dirname(new_name):
    status_item.setText("冲突:非法名称")
    continue
if new_name_counts.get(new_name, 0) > 1:
    status_item.setText("冲突:目标重复")
    continue

self.rename_tasks.append({"old_path": old_path, "new_path": new_path, "row": row})

is_valid_dirname 简单过滤非法字符:

python 复制代码
def is_valid_dirname(self, name):
    if name is None:
        return False
    if name == "":
        return False
    invalid_chars = set('<>:"/\\|?*')
    return not any(ch in invalid_chars for ch in name)

2. 两阶段重命名:避免互相覆盖

执行重命名时采用了一个「两阶段」策略:

  1. 第一阶段:把所有要改名的目录先重命名到一个临时名字(加上随机后缀)
  2. 第二阶段:再从这些临时名字统一改成目标新名字

这样就不会出现「A 改成 B,导致原来的 B 被挡住」的问题。

第一阶段:

python 复制代码
temp_map = {}
stage1_ok = []

for t in valid_tasks:
    old_path = t["old_path"]
    row = t["row"]
    item = self.table.item(row, 2)
    tmp_path = f"{old_path}.__renametmp__{uuid.uuid4().hex}"
    try:
        os.rename(old_path, tmp_path)
        temp_map[tmp_path] = t
        stage1_ok.append(tmp_path)
    except Exception as e:
        msg = f"失败:{e}"
        item.setText(msg)

第二阶段:

python 复制代码
for tmp_path in stage1_ok:
    t = temp_map[tmp_path]
    new_path = t["new_path"]
    row = t["row"]
    item = self.table.item(row, 2)
    try:
        os.rename(tmp_path, new_path)
        item.setText("成功")
    except Exception as e:
        item.setText(f"失败:{e}")
        try:
            os.rename(tmp_path, t["old_path"])
        except Exception:
            pass

这种方式的好处:

  • 可以安全处理「互换名字」等复杂情况
  • 即便第二阶段失败,也可以尝试把临时目录改回去,尽量保持原状

五、让界面更好看一点:简单的 PyQt5 美化

界面美化主要从三个方面入手:

  1. 使用 Qt 的 Fusion 风格
  2. 统一字体、背景、卡片式分组
  3. 按钮和表格的细节样式

设置 Fusion 风格和字体:

python 复制代码
def apply_theme(self):
    QApplication.setStyle(QStyleFactory.create("Fusion"))
    font = QFont()
    font.setPointSize(10)
    QApplication.setFont(font)
    self.setStyleSheet(
        """
        QMainWindow { background: #f5f7fb; }
        QGroupBox {
            background: #ffffff;
            border: 1px solid #e2e8f0;
            border-radius: 10px;
            margin-top: 8px;
        }
        QGroupBox::title {
            subcontrol-origin: margin;
            left: 12px;
            padding: 0 6px;
            color: #334155;
            font-weight: 600;
        }
        QLabel { color: #334155; }
        QLineEdit, QComboBox {
            background: #ffffff;
            border: 1px solid #cbd5e1;
            border-radius: 8px;
            padding: 7px 10px;
            min-height: 20px;
        }
        QLineEdit:focus, QComboBox:focus { border: 1px solid #4f46e5; }
        QPushButton {
            background: #4f46e5;
            color: #ffffff;
            border: none;
            border-radius: 10px;
            padding: 8px 14px;
            font-weight: 600;
        }
        QPushButton:hover { background: #4338ca; }
        QPushButton:pressed { background: #3730a3; }
        QPushButton:disabled { background: #94a3b8; color: #f8fafc; }
        QTableWidget {
            background: #ffffff;
            border: 1px solid #e2e8f0;
            border-radius: 10px;
            selection-background-color: #e0e7ff;
            selection-color: #0f172a;
            gridline-color: transparent;
        }
        QHeaderView::section {
            background: #f1f5f9;
            color: #334155;
            border: none;
            padding: 8px 10px;
            font-weight: 600;
        }
        QTableWidget::item { padding-left: 8px; padding-right: 8px; }
        QStatusBar { background: transparent; color: #475569; }
        """
    )

同时,用 QStatusBar 提示当前状态,比如:

  • 预览完成:可执行多少条重命名
  • 新增了几条规则
python 复制代码
self.setStatusBar(self.statusBar())
self.statusBar().showMessage("选择一个目录后点击"预览",确认无误再执行重命名")

六、使用 PyInstaller 打包成单文件 EXE

工具完成之后,为了方便在没有 Python 环境的电脑上使用,需要打包成 EXE。这里用的是 PyInstaller。

假设目录结构:

text 复制代码
D:\测试\Github项目\61-目录批量改名\
  ├─ renamedir.py
  ├─ 重命名.png
  └─ ...

1. 把 PNG 转成 ICO(用于 EXE 图标)

PyInstaller 的 --icon 参数需要 .ico 文件,所以先用 Pillow 转一下:

bash 复制代码
cd /d "D:\测试\Github项目\61-目录批量改名"

python -m pip install pillow

python -c "from PIL import Image; Image.open('重命名.png').save('重命名.ico')"

这样目录下就会多出一个 重命名.ico

2. 打包命令

在同一个目录下执行:

bash 复制代码
pyinstaller -F -w --clean ^
  --name "renamedir" ^
  --icon "重命名.ico" ^
  "renamedir.py"

参数含义:

  • -F:单文件 EXE
  • -w:不弹出控制台窗口
  • --clean:打包时清理缓存
  • --name "renamedir":生成的 exe 名叫 renamedir.exe
  • --icon "重命名.ico":给 exe 加上自定义图标

打包完成后,会生成:

text 复制代码
D:\测试\Github项目\61-目录批量改名\
  ├─ build\
  ├─ dist\
  │   └─ renamedir.exe
  └─ renamedir.spec

双击 dist\renamedir.exe 就可以像普通 Windows 程序一样使用了。

七、总结与可扩展方向

这个小工具本质上是一个「批量字符串处理器 + 可视化预览」,换到别的场景也非常通用:

  • 把「目录名」换成「文件名」,立即变成批量改文件名工具
  • 把规则扩展成:
    • 大小写转换(全部大写、全部小写、首字母大写)
    • 正则替换(使用 re.sub
    • 基于数字的自动编号等

如果继续扩展,可以考虑:

  • 增加「递归子目录」选项(深度遍历)
  • 增加「撤销」「导出重命名日志」功能
  • 把规则保存/加载为配置文件(比如 json),方便复用

希望这篇文章能帮你快速搭出自己的 PyQt5 小工具 + EXE 打包流程

如果你也有类似的批量处理需求,可以在这个基础上随意魔改成适合自己的版本。

结尾

希望对初学者有帮助;致力于办公自动化的小小程序员一枚
希望能得到大家的【❤️一个免费关注❤️】感谢!
求个 🤞 关注 🤞 +❤️ 喜欢 ❤️ +👍 收藏 👍
此外还有办公自动化专栏,欢迎大家订阅:Python办公自动化专栏
此外还有爬虫专栏,欢迎大家订阅:Python爬虫基础专栏
此外还有Python基础专栏,欢迎大家订阅:Python基础学习专栏
相关推荐
阿蒙Amon2 小时前
C#每日面试题-简述类成员
开发语言·c#
sunfove2 小时前
Python制作小游戏:用线性代数思想构建 2048 游戏引擎
python·线性代数·游戏引擎
小二·2 小时前
Python Web 开发进阶实战:时空数据引擎 —— 在 Flask + Vue 中构建实时地理围栏与轨迹分析系统
前端·python·flask
Uncertainty!!2 小时前
pycharm本地Failed to open X display(exiting)
ide·python·pycharm
小二·2 小时前
Python Web 开发进阶实战:可验证网络 —— 在 Flask + Vue 中实现去中心化身份(DID)与零知识证明(ZKP)认证
前端·网络·python
阿蒙Amon2 小时前
C#每日面试题-ValueTuple和Tuple的区别
开发语言·c#
百***78752 小时前
一步API+GPT-5.2生产级落地指南:架构设计+高可用+成本控制
开发语言·gpt·架构
Vallelonga2 小时前
Rust 中 extern “C“ 关键字
c语言·开发语言·rust
勇往直前plus2 小时前
解决:pycharm运行程序时出现Run ‘python tests for XXX.py‘的问题
ide·python·pycharm