# Python Tkinter实现本地文件批量重命名工具(GUI版)


一、背景介绍

在日常工作和生活中,我们经常需要对大量文件进行重命名操作(比如整理照片、文档分类)。手动逐个修改不仅效率低下,还容易出现序号错误或重复。因此,一款简单易用的批量重命名工具能极大提升效率,同时降低出错风险。

本文将使用Python内置的Tkinter库开发一款本地文件批量重命名工具,支持三种常用规则(序号、替换、日期),并提供预览、确认、执行的安全流程。工具无需额外安装依赖,开箱即用。

二、思路分析

工具的核心流程为:选择文件夹→配置规则→预览效果→确认执行→反馈结果。以下是各功能模块的实现思路:

1. 文件夹选择

通过filedialog.askdirectory获取用户选择的文件夹路径,遍历路径下的所有文件(排除子文件夹),记录文件列表。

2. 规则配置

提供三种规则,通过单选按钮切换:
序号规则:用户输入前缀、起始序号、序号位数、是否保留后缀。生成新文件名时,用str.zfill()补零保证序号位数一致。
替换规则:用户输入旧字符串和新字符串,通过str.replace()替换原文件名中的指定内容。
日期规则:用户选择日期位置(前/后)和格式(YYYY-MM-DDYYYYMMDD),用datetime.now().strftime()生成当前日期并拼接文件名。

3. 预览功能

根据当前规则生成新文件名,显示在列表框中(仅展示不修改实际文件),帮助用户验证规则正确性。

4. 执行操作

用户确认后,遍历文件列表执行os.rename()操作,捕获异常(如文件已存在、权限不足),统计成功/失败数量并反馈。

5. 安全保障

  • 预览时不修改文件;
  • 执行前弹出确认对话框;
  • 处理异常避免程序崩溃。

三、完整代码实现

以下是可直接运行的完整代码,包含详细注释:

import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
import os
from datetime import datetime

class BatchRenameTool:
    def __init__(self, root):
        self.root = root
        self.root.title("批量重命名工具")
        self.root.geometry("700x500")  # 设置窗口大小

        # 全局变量
        self.folder_path = ""
        self.files = []  # 存储文件夹中的文件列表
        self.rule_type = tk.StringVar(value="序号规则")  # 默认选中序号规则

        # 初始化控件
        self.create_widgets()

    def create_widgets(self):
        """构建GUI界面"""
        # 1. 文件夹选择区
        folder_frame = tk.Frame(self.root)
        folder_frame.pack(pady=10, fill=tk.X, padx=20)
        tk.Label(folder_frame, text="目标文件夹:").pack(side=tk.LEFT)
        self.folder_label = tk.Label(folder_frame, text="未选择", fg="gray")
        self.folder_label.pack(side=tk.LEFT, padx=5)
        tk.Button(folder_frame, text="选择文件夹", command=self.select_folder).pack(side=tk.RIGHT)

        # 2. 规则选择区
        rule_frame = tk.Frame(self.root)
        rule_frame.pack(pady=5, fill=tk.X, padx=20)
        tk.Label(rule_frame, text="规则类型:").pack(side=tk.LEFT)
        for rule in ["序号规则", "替换规则", "日期规则"]:
            tk.Radiobutton(rule_frame, text=rule, variable=self.rule_type, value=rule, command=self.update_param_frame).pack(side=tk.LEFT, padx=10)

        # 3. 参数输入区(动态切换)
        self.param_container = tk.Frame(self.root)
        self.param_container.pack(pady=10, fill=tk.X, padx=20)
        self.init_param_frames()  # 初始化所有规则的参数框架
        self.update_param_frame()  # 默认显示序号规则参数

        # 4. 预览区
        preview_frame = tk.Frame(self.root)
        preview_frame.pack(pady=10, fill=tk.BOTH, expand=True, padx=20)
        tk.Label(preview_frame, text="预览结果(原文件名 → 新文件名):").pack(anchor=tk.W)
        self.preview_list = tk.Listbox(preview_frame, width=80, height=10, font=("Arial", 10))
        self.preview_list.pack(fill=tk.BOTH, expand=True, pady=5)
        tk.Button(preview_frame, text="生成预览", command=self.generate_preview).pack(side=tk.RIGHT)

        # 5. 执行区
        execute_frame = tk.Frame(self.root)
        execute_frame.pack(pady=10, fill=tk.X, padx=20)
        self.result_label = tk.Label(execute_frame, text="", fg="blue")
        self.result_label.pack(side=tk.LEFT)
        tk.Button(execute_frame, text="执行重命名", command=self.execute_rename, bg="#4CAF50", fg="white").pack(side=tk.RIGHT)

    def init_param_frames(self):
        """初始化所有规则的参数框架(默认隐藏)"""
        # 序号规则参数框架
        self.seq_frame = tk.Frame(self.param_container)
        tk.Label(self.seq_frame, text="前缀:").grid(row=0, column=0, padx=5, pady=5)
        self.seq_prefix = tk.Entry(self.seq_frame, width=10)
        self.seq_prefix.grid(row=0, column=1, padx=5, pady=5)
        self.seq_prefix.insert(0, "File_")  # 默认前缀

        tk.Label(self.seq_frame, text="起始序号:").grid(row=0, column=2, padx=5, pady=5)
        self.seq_start = tk.Spinbox(self.seq_frame, from_=1, to=1000, width=5)
        self.seq_start.grid(row=0, column=3, padx=5, pady=5)
        self.seq_start.delete(0, tk.END)
        self.seq_start.insert(0, "1")

        tk.Label(self.seq_frame, text="序号位数:").grid(row=0, column=4, padx=5, pady=5)
        self.seq_digits = tk.Spinbox(self.seq_frame, from_=1, to=5, width=5)
        self.seq_digits.grid(row=0, column=5, padx=5, pady=5)
        self.seq_digits.delete(0, tk.END)
        self.seq_digits.insert(0, "2")

        self.seq_keep_ext = tk.BooleanVar(value=True)
        tk.Checkbutton(self.seq_frame, text="保留后缀", variable=self.seq_keep_ext).grid(row=0, column=6, padx=5, pady=5)

        # 替换规则参数框架
        self.replace_frame = tk.Frame(self.param_container)
        tk.Label(self.replace_frame, text="旧字符串:").grid(row=0, column=0, padx=5, pady=5)
        self.replace_old = tk.Entry(self.replace_frame, width=15)
        self.replace_old.grid(row=0, column=1, padx=5, pady=5)

        tk.Label(self.replace_frame, text="新字符串:").grid(row=0, column=2, padx=5, pady=5)
        self.replace_new = tk.Entry(self.replace_frame, width=15)
        self.replace_new.grid(row=0, column=3, padx=5, pady=5)

        # 日期规则参数框架
        self.date_frame = tk.Frame(self.param_container)
        self.date_pos = tk.StringVar(value="前缀")
        tk.Radiobutton(self.date_frame, text="日期在前", variable=self.date_pos, value="前缀").grid(row=0, column=0, padx=5, pady=5)
        tk.Radiobutton(self.date_frame, text="日期在后", variable=self.date_pos, value="后缀").grid(row=0, column=1, padx=5, pady=5)

        self.date_fmt = tk.StringVar(value="YYYY-MM-DD")
        tk.Radiobutton(self.date_frame, text="YYYY-MM-DD", variable=self.date_fmt, value="YYYY-MM-DD").grid(row=0, column=2, padx=5, pady=5)
        tk.Radiobutton(self.date_frame, text="YYYYMMDD", variable=self.date_fmt, value="YYYYMMDD").grid(row=0, column=3, padx=5, pady=5)

    def update_param_frame(self):
        """根据当前规则类型显示对应的参数框架"""
        # 隐藏所有参数框架
        self.seq_frame.pack_forget()
        self.replace_frame.pack_forget()
        self.date_frame.pack_forget()

        # 显示当前规则的框架
        rule = self.rule_type.get()
        if rule == "序号规则":
            self.seq_frame.pack(fill=tk.X)
        elif rule == "替换规则":
            self.replace_frame.pack(fill=tk.X)
        elif rule == "日期规则":
            self.date_frame.pack(fill=tk.X)

    def select_folder(self):
        """选择目标文件夹并更新文件列表"""
        path = filedialog.askdirectory()
        if not path:
            return
        self.folder_path = path
        self.folder_label.config(text=path, fg="black")

        # 获取文件夹中的所有文件(排除子文件夹)
        self.files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
        messagebox.showinfo("提示", f"成功加载 {len(self.files)} 个文件!")

    def generate_preview(self):
        """根据当前规则生成预览结果"""
        self.preview_list.delete(0, tk.END)
        if not self.folder_path or not self.files:
            messagebox.showwarning("警告", "请先选择文件夹!")
            return

        rule = self.rule_type.get()
        preview_data = []

        if rule == "序号规则":
            # 获取序号规则参数
            prefix = self.seq_prefix.get().strip()
            start = int(self.seq_start.get())
            digits = int(self.seq_digits.get())
            keep_ext = self.seq_keep_ext.get()

            for idx, file in enumerate(self.files):
                name, ext = os.path.splitext(file)
                new_name = f"{prefix}{str(start + idx).zfill(digits)}"
                if keep_ext:
                    new_name += ext
                preview_data.append(f"{file} → {new_name}")

        elif rule == "替换规则":
            # 获取替换规则参数
            old_str = self.replace_old.get().strip()
            new_str = self.replace_new.get().strip()

            for file in self.files:
                new_name = file.replace(old_str, new_str)
                preview_data.append(f"{file} → {new_name}")

        elif rule == "日期规则":
            # 获取日期规则参数
            pos = self.date_pos.get()
            fmt = self.date_fmt.get()
            date_str = datetime.now().strftime(fmt)

            for file in self.files:
                name, ext = os.path.splitext(file)
                if pos == "前缀":
                    new_name = f"{date_str}_{name}{ext}"
                else:
                    new_name = f"{name}_{date_str}{ext}"
                preview_data.append(f"{file} → {new_name}")

        # 显示预览结果
        for item in preview_data:
            self.preview_list.insert(tk.END, item)
        self.result_label.config(text=f"预览完成,共 {len(preview_data)} 条记录")

    def execute_rename(self):
        """执行批量重命名操作"""
        if not self.folder_path or not self.files:
            messagebox.showwarning("警告", "请先选择文件夹并生成预览!")
            return

        # 确认执行
        confirm = messagebox.askyesno("确认执行", "是否确认执行重命名?操作不可逆!")
        if not confirm:
            return

        rule = self.rule_type.get()
        success = 0
        fail = 0

        try:
            if rule == "序号规则":
                prefix = self.seq_prefix.get().strip()
                start = int(self.seq_start.get())
                digits = int(self.seq_digits.get())
                keep_ext = self.seq_keep_ext.get()

                for idx, file in enumerate(self.files):
                    old_path = os.path.join(self.folder_path, file)
                    name, ext = os.path.splitext(file)
                    new_name = f"{prefix}{str(start + idx).zfill(digits)}"
                    if keep_ext:
                        new_name += ext
                    new_path = os.path.join(self.folder_path, new_name)

                    if os.path.exists(new_path):
                        fail +=1
                        continue
                    os.rename(old_path, new_path)
                    success +=1

            elif rule == "替换规则":
                old_str = self.replace_old.get().strip()
                new_str = self.replace_new.get().strip()

                for file in self.files:
                    old_path = os.path.join(self.folder_path, file)
                    new_name = file.replace(old_str, new_str)
                    new_path = os.path.join(self.folder_path, new_name)

                    if os.path.exists(new_path):
                        fail +=1
                        continue
                    os.rename(old_path, new_path)
                    success +=1

            elif rule == "日期规则":
                pos = self.date_pos.get()
                fmt = self.date_fmt.get()
                date_str = datetime.now().strftime(fmt)

                for file in self.files:
                    old_path = os.path.join(self.folder_path, file)
                    name, ext = os.path.splitext(file)
                    if pos == "前缀":
                        new_name = f"{date_str}_{name}{ext}"
                    else:
                        new_name = f"{name}_{date_str}{ext}"
                    new_path = os.path.join(self.folder_path, new_name)

                    if os.path.exists(new_path):
                        fail +=1
                        continue
                    os.rename(old_path, new_path)
                    success +=1

        except Exception as e:
            messagebox.showerror("错误", f"执行失败:{str(e)}")
            return

        # 反馈结果
        messagebox.showinfo("执行结果", f"成功:{success} 个文件\n失败:{fail} 个文件(重复或权限问题)")
        self.result_label.config(text=f"执行完成:成功 {success} / 失败 {fail}")


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

四、总结

本文实现的批量重命名工具具备以下特点:
易用性:GUI界面直观,操作流程清晰;
安全性:预览+确认两步保障,避免误操作;
灵活性:支持三种常用规则,覆盖大多数场景;
轻量性:内置依赖,无需额外安装。

后续可扩展的功能包括:递归处理子文件夹、支持正则表达式替换、添加撤销功能等。通过开发这款工具,我们掌握了Tkinter GUI设计、文件系统操作、字符串处理等核心技能,为后续Python桌面应用开发打下基础。

希望这款工具能帮助你提高文件管理效率!
“`


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注