一、背景介绍
在日常工作和生活中,我们经常需要对大量文件进行重命名操作(比如整理照片、文档分类)。手动逐个修改不仅效率低下,还容易出现序号错误或重复。因此,一款简单易用的批量重命名工具能极大提升效率,同时降低出错风险。
本文将使用Python内置的Tkinter库开发一款本地文件批量重命名工具,支持三种常用规则(序号、替换、日期),并提供预览、确认、执行的安全流程。工具无需额外安装依赖,开箱即用。
二、思路分析
工具的核心流程为:选择文件夹→配置规则→预览效果→确认执行→反馈结果。以下是各功能模块的实现思路:
1. 文件夹选择
通过filedialog.askdirectory获取用户选择的文件夹路径,遍历路径下的所有文件(排除子文件夹),记录文件列表。
2. 规则配置
提供三种规则,通过单选按钮切换:
– 序号规则:用户输入前缀、起始序号、序号位数、是否保留后缀。生成新文件名时,用str.zfill()补零保证序号位数一致。
– 替换规则:用户输入旧字符串和新字符串,通过str.replace()替换原文件名中的指定内容。
– 日期规则:用户选择日期位置(前/后)和格式(YYYY-MM-DD或YYYYMMDD),用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桌面应用开发打下基础。
希望这款工具能帮助你提高文件管理效率!
“`