我为重命名哭了三次,后来用Python做了这个神器!

‍前言:一段感人的“文件命名”血泪史

还记得那天深夜,我对着那一百多张名为 IMG_2023xxxx.jpg 的照片,眼神逐渐涣散。手动改名字?做不到。我甚至用 Excel 写过公式,复制粘贴一个小时,手都抖了。

直到我灵魂一问:“Python,你不是号称能自动化一切吗?”

于是,一个有界面、能拖拽、还能智能按模板重命名的工具就诞生了。今天这篇文章,就来带你看看我这个“从泪水中诞生的神器”——批量文件重命名工具 的完整实现。

我为重命名哭了三次,后来用Python做了这个神器!_第1张图片

我为重命名哭了三次,后来用Python做了这个神器!_第2张图片

项目简介

该工具是基于 tkinter 开发的轻量级桌面应用,支持:

  • 删除/添加指定文字

  • 文件名批量添加编号

  • 替换关键词或整名替换

  • 使用模板批量命名

  • 支持预览 & 执行操作

  • 自定义文件类型与排序方式

界面友好、操作直观,适合处理照片、文档、素材等文件重命名场景。


️项目界面概览

工具启动后,将显示以下几个区域:

  • 选择目录 + 过滤条件(扩展名、排序方式)

  • 多种命名模式选择(6种)

  • ⚙️ 每种模式独立的参数输入区

  • 文件预览 & 重命名预览

  • ✅ 执行重命名按钮


项目结构与功能模块拆解

1️⃣ 目录选择 + 文件过滤

self.current_dir = tk.StringVar()
self.file_ext = tk.StringVar(value="*")
self.sort_method = tk.StringVar(value="name")
  • browse_directory():选择文件夹后立即预览文件

  • 支持过滤指定扩展名,如 .jpg.txt

  • 支持按文件名 / 创建时间 / 大小排序

适合处理特定批次的文件,如只修改 JPG 图片,不影响其他类型


2️⃣ 多种重命名模式(核心)

self.quick_mode = tk.StringVar(value="remove_text")

一共有六种重命名策略,用户可自由切换:

模式 功能说明
remove_text 删除文件名中指定字符
add_text 在前/后添加自定义文字
add_number 添加编号:01_文件名.ext
replace_text 批量替换关键词
replace_all 直接替换成一个新名字(保留扩展名)
template_replace 完全自定义模板 + 序号拼接
✅ 参数输入采用智能控件

每种模式在参数区会动态显示对应设置项,例如:

  • replace_text 模式下会启用“区分大小写”“全词匹配”选项

  • add_text 支持使用 [前缀] / [后缀] 指定插入位置


3️⃣ 文件预览与重命名预览

def preview_files(): ...
def preview_changes(): ...
  • preview_files() 会列出当前目录下的目标文件

  • preview_changes() 会显示 原文件名 -> 新文件名 对照

  • 所有预览结果用 ScrolledText 可视化显示,方便用户检查


4️⃣ 文件命名逻辑实现(核心)

def generate_new_name(filename): ...

根据所选模式和参数,生成新的文件名。

例如:

  • 替换关键词模式下:用 re.sub 完成大小写和词边界判断

  • 模板命名模式下:支持 {编号}_{模板名}{模板名}_{编号}

所有模式在 perform_rename() 中统一执行重命名操作。

✅实用亮点小结

功能 优势
多种模式灵活选择 无需写代码,点击即可切换
预览机制 不会误操作,先看再改
自定义过滤 + 排序 精准筛选需要处理的文件
图形界面操作 适合非技术用户
可扩展性强 后续可增加预设模板或批处理日志

运行效果演示

 
  
python file_renamer_gui.py

或者将其打包为桌面应用:

pyinstaller -F -w file_renamer_gui.py


依赖安装

本项目仅依赖标准库,无需额外 pip 安装。开箱即用!


以下是完整代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
简易批量文件重命名工具
提供直观的图形界面和常用重命名模式
"""

import os
import re
import sys
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
from datetime import datetime
import shutil

class FileRenamerApp:
    def __init__(self, root):
        self.root = root
        # 增加前端初始高度 50%
        width = 900
        height = int(700 * 1)
        self.root.title("简易文件批量重命名工具")
        self.root.geometry(f"{width}x{height}")
        self.root.minsize(width, height)

        # 设置整体样式
        self.style = ttk.Style()
        self.style.configure("Big.TButton", font=("微软雅黑", 12, "bold"), padding=10)
        self.style.configure("TButton", font=("微软雅黑", 10), padding=5)
        self.style.configure("TLabel", font=("微软雅黑", 10))
        self.style.configure("Title.TLabel", font=("微软雅黑", 12, "bold"))
        self.style.configure("TRadiobutton", font=("微软雅黑", 10))

        # 变量初始化
        self.current_dir = tk.StringVar()
        self.rename_mode = tk.StringVar(value="quick_mode")
        self.quick_mode = tk.StringVar(value="remove_prefix")
        self.preview_mode = tk.BooleanVar(value=True)

        # 快速模式的变量
        self.remove_text = tk.StringVar()  # 要删除的文本
        self.custom_text = tk.StringVar()  # 自定义添加的文本
        self.start_number = tk.IntVar(value=1)  # 起始序号
        self.find_text = tk.StringVar()
        self.replace_with = tk.StringVar()
        # 新增替换选项变量
        self.case_sensitive = tk.BooleanVar(value=True)
        self.whole_word = tk.BooleanVar(value=False)

        # 新增文件类型过滤和排序相关变量
        self.file_ext = tk.StringVar(value="*")
        self.sort_method = tk.StringVar(value="name")

        # 新增替换原名称变量
        self.replace_all_name = tk.StringVar()

        # 新增模板替换相关变量
        self.new_template = tk.StringVar()
        self.number_position = tk.StringVar(value="before")  # before 或 after

        # 创建主界面
        self.create_main_frame()

        # 文件列表
        self.files = []
        self.renamed_files = []

        # 默认选择当前目录
        self.current_dir.set(os.getcwd())

    def create_main_frame(self):
        """创建主界面"""
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 添加垂直滚动条
        scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        canvas = tk.Canvas(main_frame, yscrollcommand=scrollbar.set)
        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar.config(command=canvas.yview)

        frame = ttk.Frame(canvas)
        canvas.create_window((0, 0), window=frame, anchor=tk.NW)

        frame.bind("", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))

        # 顶部区域 - 目录选择
        top_frame = ttk.Frame(frame, padding=10)
        top_frame.pack(fill=tk.X)

        ttk.Label(top_frame, text="第一步:选择要重命名的文件所在目录", style="Title.TLabel").pack(anchor=tk.W, pady=(0, 5))

        dir_frame = ttk.Frame(top_frame)
        dir_frame.pack(fill=tk.X)
        ttk.Entry(dir_frame, textvariable=self.current_dir, width=70).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(dir_frame, text="浏览文件夹", command=self.browse_directory).pack(side=tk.LEFT)

        # 新增文件类型过滤和排序选择
        filter_frame = ttk.Frame(top_frame)
        filter_frame.pack(fill=tk.X, pady=5)
        ttk.Label(filter_frame, text="文件类型:").pack(side=tk.LEFT, padx=5)
        ttk.Entry(filter_frame, textvariable=self.file_ext, width=10).pack(side=tk.LEFT, padx=5)
        ttk.Label(filter_frame, text="(例如: .txt, 输入 * 表示所有类型)").pack(side=tk.LEFT, padx=5)
        ttk.Label(filter_frame, text="排序方式:").pack(side=tk.LEFT, padx=20)
        ttk.Combobox(filter_frame, textvariable=self.sort_method, values=["name", "create_time", "size"], width=15).pack(side=tk.LEFT, padx=5)

        # 中间区域 - 重命名模式选择
        middle_frame = ttk.LabelFrame(frame, text="第二步:选择重命名方式", padding=10)
        middle_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # 快速模式选项
        quick_frame = ttk.Frame(middle_frame)
        quick_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        # 创建五个快速模式选项
        modes = [
            ("删除指定文字", "remove_text", "删除文件名中的指定文字\n例如:删除'IMG_'、'照片'等"),
            ("添加文字", "add_text", "在文件名前面或后面添加文字\n例如:添加'假期照片_'"),
            ("添加序号", "add_number", "给文件添加序号\n例如:01_文件名、02_文件名"),
            ("替换文字", "replace_text", "将文件名中的某些文字替换成其他文字\n例如:将'图片'替换为'照片'"),
            ("替换原名称", "replace_all", "将原文件名全部替换"),
            ("全模板替换(推荐!!)", "template_replace", "使用新模板替换原文件名,并可添加序号")
        ]

        for i, (text, value, desc) in enumerate(modes):
            frame_mode = ttk.Frame(quick_frame, padding=5)
            frame_mode.grid(row=i // 2, column=i % 2, sticky=tk.NSEW, padx=5, pady=5)

            ttk.Radiobutton(frame_mode, text=text, value=value,
                            variable=self.quick_mode).pack(anchor=tk.W)
            ttk.Label(frame_mode, text=desc, wraplength=350).pack(anchor=tk.W, padx=(20, 0))

        # 设置参数区域
        param_frame = ttk.LabelFrame(frame, text="第三步:设置参数", padding=10)
        param_frame.pack(fill=tk.X, padx=10, pady=5)

        # 使用字典存储不同模式的参数控件
        self.param_widgets = {}

        # 删除文字模式的参数
        remove_frame = ttk.Frame(param_frame)
        self.param_widgets["remove_text"] = remove_frame
        ttk.Label(remove_frame, text="要删除的文字:").pack(side=tk.LEFT, padx=5)
        ttk.Entry(remove_frame, textvariable=self.remove_text, width=40).pack(side=tk.LEFT)
        ttk.Label(remove_frame, text="(可以输入多个要删除的文字,用逗号分隔)").pack(side=tk.LEFT, padx=5)

        # 添加文字模式的参数
        add_frame = ttk.Frame(param_frame)
        self.param_widgets["add_text"] = add_frame
        ttk.Label(add_frame, text="要添加的文字:").pack(side=tk.LEFT, padx=5)
        ttk.Entry(add_frame, textvariable=self.custom_text, width=40).pack(side=tk.LEFT)
        ttk.Label(add_frame, text="(可以使用[前缀]或[后缀]表示添加位置)").pack(side=tk.LEFT, padx=5)

        # 添加序号模式的参数
        number_frame = ttk.Frame(param_frame)
        self.param_widgets["add_number"] = number_frame
        ttk.Label(number_frame, text="起始序号:").pack(side=tk.LEFT, padx=5)
        ttk.Spinbox(number_frame, from_=0, to=999, width=5, textvariable=self.start_number).pack(side=tk.LEFT)
        ttk.Label(number_frame, text="(序号将自动补零,如:01、02、03...)").pack(side=tk.LEFT, padx=5)

        # 替换文字模式的参数
        replace_frame = ttk.Frame(param_frame)
        self.param_widgets["replace_text"] = replace_frame
        ttk.Label(replace_frame, text="查找内容:").grid(row=0, column=0, padx=5)
        ttk.Entry(replace_frame, textvariable=self.find_text, width=20).grid(row=0, column=1, padx=5)
        ttk.Label(replace_frame, text="替换为:").grid(row=0, column=2, padx=5)
        ttk.Entry(replace_frame, textvariable=self.replace_with, width=20).grid(row=0, column=3, padx=5)

        # 添加替换选项复选框
        ttk.Checkbutton(replace_frame, text="区分大小写", variable=self.case_sensitive
                        ).grid(row=1, column=0, columnspan=2, sticky=tk.W, padx=5)
        ttk.Checkbutton(replace_frame, text="全词匹配", variable=self.whole_word
                        ).grid(row=1, column=2, columnspan=2, sticky=tk.W, padx=5)

        # 替换原名称模式的参数
        replace_all_frame = ttk.Frame(param_frame)
        self.param_widgets["replace_all"] = replace_all_frame
        ttk.Label(replace_all_frame, text="新名称:").pack(side=tk.LEFT, padx=5)
        ttk.Entry(replace_all_frame, textvariable=self.replace_all_name, width=40).pack(side=tk.LEFT)
        ttk.Label(replace_all_frame, text="(不包含扩展名)").pack(side=tk.LEFT, padx=5)

        # 模板替换模式的参数
        template_frame = ttk.Frame(param_frame)
        self.param_widgets["template_replace"] = template_frame
        ttk.Label(template_frame, text="新模板:").pack(side=tk.LEFT, padx=5)
        ttk.Entry(template_frame, textvariable=self.new_template, width=40).pack(side=tk.LEFT)
        ttk.Label(template_frame, text="序号位置:").pack(side=tk.LEFT, padx=20)
        ttk.Combobox(template_frame, textvariable=self.number_position, values=["before", "after"], width=10).pack(side=tk.LEFT, padx=5)

        # 显示默认模式的参数控件
        self.show_param_widgets("remove_text")

        # 绑定模式切换事件
        self.quick_mode.trace('w', self.on_mode_change)

        # 文件预览区域及按钮
        file_preview_frame = ttk.LabelFrame(frame, text="所选文件预览", padding=10)
        file_preview_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        file_preview_button_frame = ttk.Frame(file_preview_frame)
        file_preview_button_frame.pack(fill=tk.X)
        ttk.Button(file_preview_button_frame, text="预览文件", command=self.preview_files,
                   style="Big.TButton").pack(side=tk.LEFT, padx=5)

        self.file_preview_text = ScrolledText(file_preview_frame, height=8)
        self.file_preview_text.pack(fill=tk.BOTH, expand=True)

        # 预览更改区域及按钮
        preview_frame = ttk.LabelFrame(frame, text="第四步:预览更改", padding=10)
        preview_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        preview_button_frame = ttk.Frame(preview_frame)
        preview_button_frame.pack(fill=tk.X)
        ttk.Button(preview_button_frame, text="预览变更", command=self.preview_changes,
                   style="Big.TButton").pack(side=tk.LEFT, padx=5)

        self.preview_text = ScrolledText(preview_frame, height=8)
        self.preview_text.pack(fill=tk.BOTH, expand=True)

        # 底部按钮区域
        button_frame = ttk.Frame(frame, padding=10)
        button_frame.pack(fill=tk.X)

        ttk.Button(button_frame, text="执行重命名", command=self.perform_rename,
                   style="Big.TButton").pack(side=tk.LEFT, padx=5)

    def show_param_widgets(self, mode):
        """显示指定模式的参数控件"""
        # 隐藏所有参数控件
        for frame in self.param_widgets.values():
            frame.pack_forget()

        # 显示指定模式的参数控件
        if mode in self.param_widgets:
            self.param_widgets[mode].pack(fill=tk.X, pady=5)

    def on_mode_change(self, *args):
        """模式切换时的处理函数"""
        self.show_param_widgets(self.quick_mode.get())

    def browse_directory(self):
        """浏览并选择目录"""
        directory = filedialog.askdirectory(title="选择要重命名文件的目录")
        if directory:
            self.current_dir.set(directory)
            self.preview_files()

    def get_files(self):
        """获取目录中的文件列表,并根据文件类型和排序方式过滤排序"""
        directory = self.current_dir.get()
        if not directory or not os.path.isdir(directory):
            messagebox.showerror("错误", "请选择有效的目录")
            return []

        all_files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]

        # 文件类型过滤
        if self.file_ext.get() != "*":
            all_files = [f for f in all_files if f.endswith(self.file_ext.get())]

        # 排序
        if self.sort_method.get() == "name":
            all_files.sort()
        elif self.sort_method.get() == "create_time":
            all_files.sort(key=lambda x: os.path.getctime(os.path.join(directory, x)))
        elif self.sort_method.get() == "size":
            all_files.sort(key=lambda x: os.path.getsize(os.path.join(directory, x)))

        return all_files

    def generate_new_name(self, filename):
        """生成新文件名"""
        if self.quick_mode.get() == "replace_text":
            find_text = self.find_text.get()
            replace_with = self.replace_with.get()

            # 处理全词匹配
            if self.whole_word.get():
                find_text = r'\b' + re.escape(find_text) + r'\b'

            # 处理大小写敏感性
            flags = 0 if self.case_sensitive.get() else re.IGNORECASE

            # 执行替换
            return re.sub(find_text, replace_with, filename, flags=flags)
        elif self.quick_mode.get() == "remove_text":
            remove_texts = self.remove_text.get().split(',')
            new_name = filename
            for text in remove_texts:
                new_name = new_name.replace(text.strip(), '')
            return new_name
        elif self.quick_mode.get() == "add_text":
            custom_text = self.custom_text.get()
            if "[前缀]" in custom_text:
                return custom_text.replace("[前缀]", "") + filename
            elif "[后缀]" in custom_text:
                name, ext = os.path.splitext(filename)
                return name + custom_text.replace("[后缀]", "") + ext
            else:
                return custom_text + filename
        elif self.quick_mode.get() == "add_number":
            index = len(self.renamed_files) + self.start_number.get()
            name, ext = os.path.splitext(filename)
            return f"{index:02d}_{name}{ext}"
        elif self.quick_mode.get() == "replace_all":
            _, ext = os.path.splitext(filename)
            return f"{self.replace_all_name.get()}{ext}"
        elif self.quick_mode.get() == "template_replace":
            index = len(self.renamed_files) + self.start_number.get()
            _, ext = os.path.splitext(filename)
            if self.number_position.get() == "before":
                return f"{index:02d}_{self.new_template.get()}{ext}"
            else:
                return f"{self.new_template.get()}_{index:02d}{ext}"
        else:
            return filename

    def preview_files(self):
        """预览所选文件"""
        self.files = self.get_files()
        self.file_preview_text.delete(1.0, tk.END)
        for file in self.files:
            self.file_preview_text.insert(tk.END, f"{file}\n")

    def preview_changes(self):
        """预览重命名更改"""
        self.files = self.get_files()
        self.renamed_files = []
        self.preview_text.delete(1.0, tk.END)
        for file in self.files:
            new_name = self.generate_new_name(file)
            self.renamed_files.append(new_name)
            self.preview_text.insert(tk.END, f"{file} -> {new_name}\n")

    def perform_rename(self):
        """执行重命名操作"""
        directory = self.current_dir.get()
        if not self.files:
            messagebox.showerror("错误", "请先预览文件")
            return
        for old, new in zip(self.files, self.renamed_files):
            old_path = os.path.join(directory, old)
            new_path = os.path.join(directory, new)
            try:
                os.rename(old_path, new_path)
            except Exception as e:
                messagebox.showerror("错误", f"重命名 {old} 失败: {str(e)}")
                return
        messagebox.showinfo("成功", "重命名操作完成")


if __name__ == "__main__":
    root = tk.Tk()
    app = FileRenamerApp(root)
    root.mainloop()

总结

这个工具解决了我多年命名文件的痛点,也是我第一次将 Python GUI 应用于真正的桌面办公自动化场景。你可以用它管理素材、照片、报告、合同、论文……只要是需要批量处理名字的,都能派上大用场。

如果你也有类似需求,欢迎 Fork / 点赞收藏 ❤️
后续我也可能加入更多“模板保存”、“规则导出”等实用功能!

你可能感兴趣的:(python,前端,javascript,开发语言)