1.效果图

2.说明
一个简洁高效的文件夹比较工具,用于快速检测两个文件夹中同名文件的大小差异。
✨ 功能特性
- 🖱️ 拖拽操作:支持直接拖拽文件夹到窗口,简单便捷
- 📊 智能比较:自动比对两个文件夹中所有同名文件的大小
- 📈 详细报告:清晰展示文件差异,包括具体的大小差值
- 🎨 友好界面:现代化的 GUI 设计,操作直观
- 📏 自动格式化:文件大小自动转换为易读的单位(B/KB/MB/GB)
- 🔄 灵活拖入:可一次拖入两个文件夹,也可分两次拖入
📋 功能说明
主要用途
- 验证文件同步是否完整
- 检查备份文件是否一致
- 对比不同版本的文件夹差异
- 确认文件复制是否成功
比较逻辑
- 只比较同名文件的大小
- 仅扫描文件夹根目录(不递归子目录)
- 如果某个文件只存在于一个文件夹中,将被忽略
- 大小相同则视为一致,不同则显示详细差异信息
🔧 安装依赖
环境要求
- Python 3.6+
- Windows/Linux/macOS
依赖包安装
pip install tkinterdnd2
注意:
tkinter通常随 Python 自带,无需额外安装
🚀 使用方法
启动程序
python 同名文件比较.pyw
或直接双击 .pyw 文件(Windows 下无命令行窗口)
操作步骤
-
拖入文件夹
- 将第一个文件夹拖入蓝色区域
- 将第二个文件夹拖入蓝色区域
- 也可以一次拖入两个文件夹
-
自动比较
- 两个文件夹都选择后,程序自动开始比较
- 比较结果实时显示在下方文本区域
-
查看结果
- ✅ 一致文件:底部显示一致文件总数
- ❌ 不一致文件:列出每个文件的详细差异信息
- 文件名
- 文件夹1 中的大小
- 文件夹2 中的大小
- 大小差值
-
重新比较
- 点击 "🔄 重新开始" 按钮清空选择
- 重新拖入文件夹进行新的比较
📊 输出示例
一致情况
🔍 正在比较:
C:\folder1
C:\folder2
🎉 所有同名文件大小完全一致!
✅ 一致的文件总数: 15
存在差异
🔍 正在比较:
C:\folder1
C:\folder2
❌ 发现 3 个文件不一致或缺失:
📄 document.pdf
文件夹1: 2.35 MB
文件夹2: 2.40 MB
差异: 51.20 KB (大小不一致)
📄 image.png
文件夹1: 856.45 KB
文件夹2: 900.12 KB
差异: 43.67 KB (大小不一致)
📄 data.xlsx
文件夹1: 15.67 MB
文件夹2: 15.67 MB
差异: 0 B (大小不一致)
--------------------------------------------------
✅ 一致的文件总数: 12
⚙️ 技术实现
核心功能
- 文件扫描:
get_folder_files_info()- 获取文件夹中所有文件信息 - 文件比较:
compare_folders()- 比对两个文件夹的文件大小 - 拖拽处理:
on_drop()- 处理拖拽事件 - 大小格式化:
format_size()- 将字节转换为可读格式
主要依赖
tkinter- GUI 界面框架tkinterdnd2- 拖拽功能支持os- 文件系统操作
📝 注意事项
- 仅比较根目录:工具不会递归检查子文件夹
- 仅比较同名文件:只关心两个文件夹中都存在的文件
- 仅比较大小:不比较文件内容,只比较文件大小
- 忽略单边文件:只存在于一个文件夹的文件会被忽略
🔍 适用场景
- ✅ 检查文件备份完整性
- ✅ 验证文件同步结果
- ✅ 对比两个版本的文件夹
- ✅ 确认文件传输准确性
- ❌ 不适合比较文件内容差异(仅比较大小)
- ❌ 不适合递归比较多层目录结构
🐛 常见问题
Q: 为什么有些文件没有显示?
A: 只有两个文件夹中都存在的同名文件才会被比较,单边存在的文件会被忽略。
Q: 可以比较子文件夹吗?
A: 当前版本只比较文件夹根目录的文件,不递归子文件夹。
Q: 文件大小一样但内容不同怎么办?
A: 此工具仅比较大小,不比较内容。如需比较内容,建议使用哈希校验工具。
📄 许可证
本工具为开源项目,可自由使用和修改。
👨💻 开发信息
- 开发语言:Python
- GUI 框架:Tkinter
- 拖拽支持:TkinterDnD2
3.python代码
import os
import sys
import tkinter as tk
import traceback
from tkinter import StringVar, scrolledtext
from tkinterdnd2 import DND_FILES, TkinterDnD
# ========== 工具函数 ==========
def get_folder_files_info(folder_path):
"""
获取文件夹中所有文件的 {文件名: 文件大小} 字典(不递归子目录)
"""
if not os.path.isdir(folder_path):
return {}
files = {}
for item in os.listdir(folder_path):
path = os.path.join(folder_path, item)
if os.path.isfile(path):
files[item] = os.path.getsize(path)
return files
def compare_folders(folder1, folder2):
"""
比较两个文件夹中的同名文件大小
返回: (same_count, differences)
"""
info1 = get_folder_files_info(folder1)
info2 = get_folder_files_info(folder2)
same_count = 0
differences = []
all_filenames = set(info1.keys()) | set(info2.keys())
for filename in all_filenames:
size1 = info1.get(filename)
size2 = info2.get(filename)
# 都不存在(理论上不会)
if size1 is None or size2 is None:
continue
# 都存在,比较大小
if size1 == size2:
same_count += 1
else:
print(size1,size2,filename)
differences.append({
'name': filename,
'size1': size1,
'size2': size2,
'diff': abs(size1 - size2),
'reason': '大小不一致'
})
return same_count, differences
# ========== 全局变量 ==========
folder1_path = ""
folder2_path = ""
root = None
def format_size(size):
"""格式化字节为 KB/MB/GB"""
if size < 1024:
return f"{size} B"
elif size < 1024 ** 2:
return f"{size / 1024:.2f} KB"
elif size < 1024 ** 3:
return f"{size / (1024**2):.2f} MB"
else:
return f"{size / (1024**3):.2f} GB"
def on_drop(event):
global folder1_path, folder2_path
# 获取拖入的路径列表
paths = root.tk.splitlist(event.data)
valid_folders = [p.strip('{}') for p in paths if os.path.isdir(p)]
if not valid_folders:
log_text.insert(tk.END, "⚠️ 拖入的不是有效文件夹,请检查。\n")
log_text.see(tk.END)
return
# 分配文件夹
assigned = 0
if not folder1_path:
folder1_path = valid_folders[0]
display1.set(f"📁 文件夹1: {os.path.basename(folder1_path)}")
assigned += 1
if len(valid_folders) > assigned and not folder2_path:
folder2_path = valid_folders[assigned]
display2.set(f"📁 文件夹2: {os.path.basename(folder2_path)}")
assigned += 1
# 更新状态
if folder1_path and folder2_path:
status_var.set("✅ 两个文件夹已准备就绪,开始比较...")
compare_and_show()
else:
status_var.set(f"🟡 已拖入 {assigned} 个文件夹,等待另一个...")
def compare_and_show():
global folder1_path, folder2_path
log_text.delete(1.0, tk.END)
if not folder1_path or not folder2_path:
log_text.insert(tk.END, "❌ 请先拖入两个文件夹。\n")
return
log_text.insert(tk.END, f"🔍 正在比较:\n {folder1_path}\n {folder2_path}\n\n")
log_text.see(tk.END)
try:
same_count, diffs = compare_folders(folder1_path, folder2_path)
if diffs:
log_text.insert(tk.END, f"❌ 发现 {len(diffs)} 个文件不一致或缺失:\n", "error")
for item in diffs:
name = item['name']
s1 = format_size(item['size1'])
s2 = format_size(item['size2'])
diff = format_size(item['diff'])
reason = item['reason']
log_text.insert(tk.END, f" 📄 {name}\n", "filename")
log_text.insert(tk.END, f" 文件夹1: {s1}\n")
log_text.insert(tk.END, f" 文件夹2: {s2}\n")
log_text.insert(tk.END, f" 差异: {diff} ({reason})\n\n")
log_text.insert(tk.END, "-" * 50 + "\n")
else:
log_text.insert(tk.END, "🎉 所有同名文件大小完全一致!\n", "success")
log_text.insert(tk.END, f"✅ 一致的文件总数: {same_count}\n")
status_var.set(f"完成:共比较 {same_count + len(diffs)} 个文件")
except Exception as e:
log_text.insert(tk.END, f"❌ 比较出错: {str(e)}\n", "error")
status_var.set("❌ 比较失败")
traceback.print_exc(e)
log_text.see(tk.END)
def reset_folders():
global folder1_path, folder2_path
folder1_path = ""
folder2_path = ""
display1.set("📁 文件夹1: 未选择")
display2.set("📁 文件夹2: 未选择")
log_text.delete(1.0, tk.END)
status_var.set("等待拖入文件夹...")
# ========== GUI 界面 ==========
root = TkinterDnD.Tk()
root.title("📂 文件夹同名文件比较工具")
root.geometry("700x500")
root.configure(bg="#F0F2F5")
# 标题
tk.Label(
root,
text="📁 同名文件大小比较",
font=("微软雅黑", 18, "bold"),
bg="#F0F2F5",
fg="#2C3E50"
).pack(pady=10)
tk.Label(
root,
text="支持一次或分次拖入两个文件夹",
font=("微软雅黑", 10),
bg="#F0F2F5",
fg="#7F8C8D"
).pack()
# 拖拽区域
drag_frame = tk.Frame(
root,
bg="#3498DB",
highlightbackground="#2980B9",
highlightthickness=2,
height=100
)
drag_frame.pack(padx=60, pady=20, fill="x")
drag_frame.pack_propagate(False)
drag_label = tk.Label(
drag_frame,
text="👇 将文件夹拖入此处\n可一次拖一个或两个",
font=("微软雅黑", 12),
bg="#3498DB",
fg="white",
justify="center"
)
drag_label.pack(expand=True)
# 注册拖拽
drag_frame.drop_target_register(DND_FILES)
drag_frame.dnd_bind('<<Drop>>', on_drop)
# 文件夹显示
display1 = StringVar(value="📁 文件夹1: 未选择")
display2 = StringVar(value="📁 文件夹2: 未选择")
tk.Label(root, textvariable=display1, bg="#F0F2F5", fg="#2980B9", font=("Consolas", 10), anchor="w").pack(pady=2, padx=30, fill="x")
tk.Label(root, textvariable=display2, bg="#F0F2F5", fg="#27AE60", font=("Consolas", 10), anchor="w").pack(pady=2, padx=30, fill="x")
# 控制按钮
btn_frame = tk.Frame(root, bg="#F0F2F5")
btn_frame.pack(pady=5)
tk.Button(btn_frame, text="🔄 重新开始", command=reset_folders, font=("微软雅黑", 9), bg="#95A5A6", fg="white").pack(side="left", padx=5)
# 状态栏
status_var = StringVar(value="等待拖入文件夹...")
status_label = tk.Label(
root,
textvariable=status_var,
bg="#ECF0F1",
fg="#7F8C8D",
font=("微软雅黑", 9),
anchor="w",
padx=10,
height=1
)
status_label.pack(padx=20, pady=5, fill="x")
# 结果输出(带滚动条)
log_text = scrolledtext.ScrolledText(
root,
font=("Consolas", 9),
bg="white",
fg="#2C3E50",
height=12
)
log_text.pack(padx=20, pady=10, fill="both", expand=True)
# 高亮样式
log_text.tag_config("error", foreground="#E74C3C", font=("Consolas", 9, "bold"))
log_text.tag_config("success", foreground="#27AE60", font=("Consolas", 9, "bold"))
log_text.tag_config("filename", foreground="#34495E", font=("Consolas", 9, "bold"))
# 版权
tk.Label(root, text="Python · 文件比较工具", font=("Consolas", 8), bg="#F0F2F5", fg="#BDC3C7").pack(side="bottom", pady=5)
# ========== 运行 ==========
if __name__ == "__main__":
root.mainloop()

324

被折叠的 条评论
为什么被折叠?



