python(51) : GUI拖两个文件夹比较同名文件大小是否相同打印不同的文件[Windows工具(5)]

1.效果图

2.说明

一个简洁高效的文件夹比较工具,用于快速检测两个文件夹中同名文件的大小差异。

✨ 功能特性

  • 🖱️ 拖拽操作:支持直接拖拽文件夹到窗口,简单便捷
  • 📊 智能比较:自动比对两个文件夹中所有同名文件的大小
  • 📈 详细报告:清晰展示文件差异,包括具体的大小差值
  • 🎨 友好界面:现代化的 GUI 设计,操作直观
  • 📏 自动格式化:文件大小自动转换为易读的单位(B/KB/MB/GB)
  • 🔄 灵活拖入:可一次拖入两个文件夹,也可分两次拖入

📋 功能说明

主要用途

  • 验证文件同步是否完整
  • 检查备份文件是否一致
  • 对比不同版本的文件夹差异
  • 确认文件复制是否成功

比较逻辑

  • 只比较同名文件的大小
  • 仅扫描文件夹根目录(不递归子目录)
  • 如果某个文件只存在于一个文件夹中,将被忽略
  • 大小相同则视为一致,不同则显示详细差异信息

🔧 安装依赖

环境要求

  • Python 3.6+
  • Windows/Linux/macOS

依赖包安装

pip install tkinterdnd2

注意tkinter 通常随 Python 自带,无需额外安装

🚀 使用方法

启动程序

python 同名文件比较.pyw

或直接双击 .pyw 文件(Windows 下无命令行窗口)

操作步骤

  1. 拖入文件夹

    • 将第一个文件夹拖入蓝色区域
    • 将第二个文件夹拖入蓝色区域
    • 也可以一次拖入两个文件夹
  2. 自动比较

    • 两个文件夹都选择后,程序自动开始比较
    • 比较结果实时显示在下方文本区域
  3. 查看结果

    • ✅ 一致文件:底部显示一致文件总数
    • ❌ 不一致文件:列出每个文件的详细差异信息
      • 文件名
      • 文件夹1 中的大小
      • 文件夹2 中的大小
      • 大小差值
  4. 重新比较

    • 点击 "🔄 重新开始" 按钮清空选择
    • 重新拖入文件夹进行新的比较

📊 输出示例

一致情况

🔍 正在比较:
  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 - 文件系统操作

📝 注意事项

  1. 仅比较根目录:工具不会递归检查子文件夹
  2. 仅比较同名文件:只关心两个文件夹中都存在的文件
  3. 仅比较大小:不比较文件内容,只比较文件大小
  4. 忽略单边文件:只存在于一个文件夹的文件会被忽略

🔍 适用场景

  • ✅ 检查文件备份完整性
  • ✅ 验证文件同步结果
  • ✅ 对比两个版本的文件夹
  • ✅ 确认文件传输准确性
  • ❌ 不适合比较文件内容差异(仅比较大小)
  • ❌ 不适合递归比较多层目录结构

🐛 常见问题

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()

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值