Python進行文件比對的不同場景實現(xiàn)方法詳解
Python提供了多個用于文件比對的庫,適用于不同的比較場景。以下是主要的文件比對庫及其特點:
1. 標準庫中的比對工具
1.1filecmp模塊
功能:文件和目錄比較
特點:
- 比較文件內(nèi)容(淺層和深層比較)
- 比較目錄結構
- 內(nèi)置
dircmp類用于目錄比較
典型用途:
import filecmp
# 文件比較
filecmp.cmp('file1.txt', 'file2.txt', shallow=False)
# 目錄比較
comparison = filecmp.dircmp('dir1', 'dir2')1.2difflib模塊
功能:文本差異比較
特點:
- 生成差異報告(unified diff, context diff等)
- 支持行級比較
- 可計算相似度比率
典型用途:
import difflib # 生成差異 diff = difflib.unified_diff(text1.splitlines(), text2.splitlines()) # 相似度比較 similarity = difflib.SequenceMatcher(None, text1, text2).ratio()
2. 第三方庫
2.1deepdiff(推薦)
功能:深度差異比較
特點:
- 支持復雜數(shù)據(jù)結構比較
- 可比較文本、字典、列表、集合等
- 提供詳細的差異報告
安裝:pip install deepdiff
典型用途:
from deepdiff import DeepDiff diff = DeepDiff(file1_content, file2_content)
2.2python-diff/diff-match-patch
功能:高級文本差異比較
特點:
- 支持多種差異算法
- 可用于文本合并
安裝:pip install diff-match-patch
典型用途:
from diff_match_patch import diff_match_patch dmp = diff_match_patch() diffs = dmp.diff_main(text1, text2)
2.3hashlib(用于快速比較)
功能:通過哈希值比較文件
特點:
快速判斷文件是否相同
支持多種哈希算法
典型用途:
import hashlib
def file_hash(filename):
h = hashlib.md5()
with open(filename, 'rb') as f:
while chunk := f.read(8192):
h.update(chunk)
return h.hexdigest()
file_hash('file1.txt') == file_hash('file2.txt')2.4pandas(用于結構化數(shù)據(jù)比較)
功能:CSV/Excel等結構化文件比較
特點:
- 表格數(shù)據(jù)比較
- 支持列級、行級比較
典型用途:
import pandas as pd
df1 = pd.read_csv('file1.csv')
df2 = pd.read_csv('file2.csv')
diff = df1.compare(df2)2.5binaryornot(二進制文件識別)
功能:識別二進制文件
特點:
- 判斷文件是否為二進制
- 避免對二進制文件進行文本比較
安裝:pip install binaryornot
典型用途:
from binaryornot.check import is_binary
if not is_binary('file.bin'):
# 進行文本比較3. 特殊用途庫
3.1pygtrie(用于文件名比較)
功能:基于前綴樹的文件名比較
特點:
- 高效比較大量文件名
- 支持前綴匹配
安裝:pip install pygtrie
3.2fuzzywuzzy(模糊比較)
功能:模糊字符串匹配
特點:
- 處理拼寫差異
- 計算相似度分數(shù)
安裝:pip install fuzzywuzzy python-Levenshtein
典型用途:
from fuzzywuzzy import fuzz
similarity = fuzz.ratio("file1.txt", "file2.txt")4. 綜合比較工具選擇建議
| 場景 | 推薦庫 | 特點 |
|---|---|---|
| 簡單文件內(nèi)容比較 | filecmp | 內(nèi)置,無需安裝 |
| 詳細文本差異 | difflib | 內(nèi)置,生成差異報告 |
| 深度結構比較 | deepdiff | 復雜數(shù)據(jù)結構比較 |
| 大型文件快速比較 | hashlib | 哈希值比較 |
| 表格數(shù)據(jù)比較 | pandas | 結構化數(shù)據(jù)比較 |
| 二進制文件處理 | binaryornot | 識別二進制文件 |
| 模糊匹配 | fuzzywuzzy | 處理名稱差異 |
5. 高級應用示例:綜合使用多個庫
import os
import filecmp
import difflib
from deepdiff import DeepDiff
from binaryornot.check import is_binary
def comprehensive_compare(file1, file2):
# 基本檢查
if os.path.getsize(file1) != os.path.getsize(file2):
print("文件大小不同")
return False
# 二進制檢查
if is_binary(file1) or is_binary(file2):
print("二進制文件,使用哈希比較")
return filecmp.cmp(file1, file2, shallow=False)
# 文本文件比較
with open(file1, 'r') as f1, open(file2, 'r') as f2:
content1 = f1.read()
content2 = f2.read()
if content1 == content2:
return True
# 生成差異報告
diff = difflib.unified_diff(
content1.splitlines(),
content2.splitlines(),
fromfile=file1,
tofile=file2
)
print('\n'.join(diff))
# 深度差異分析
ddiff = DeepDiff(content1, content2)
print(f"深度差異: {ddiff}")
return False這些庫可以根據(jù)具體需求組合使用,構建強大的文件比對工具。
Beyond Compare是一款強大的文件和文件夾比較工具,下面將使用PyQt5實現(xiàn)一個簡化版的類似功能,包含文件比較、文件夾比較和文本差異高亮顯示。
完整實現(xiàn)代碼
import sys
import os
import filecmp
import difflib
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QTextEdit, QPushButton, QFileDialog, QTreeWidget,
QTreeWidgetItem, QSplitter, QMessageBox)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor, QColor, QFont, QBrush
class FileCompareApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PyCompare - 類似Beyond Compare的工具")
self.setGeometry(100, 100, 1200, 800)
self.init_ui()
self.left_dir = ""
self.right_dir = ""
def init_ui(self):
# 主窗口布局
main_widget = QWidget()
main_layout = QVBoxLayout()
# 頂部按鈕區(qū)域
button_layout = QHBoxLayout()
self.left_file_btn = QPushButton("選擇左側文件/文件夾")
self.left_file_btn.clicked.connect(lambda: self.select_file_or_dir('left'))
button_layout.addWidget(self.left_file_btn)
self.right_file_btn = QPushButton("選擇右側文件/文件夾")
self.right_file_btn.clicked.connect(lambda: self.select_file_or_dir('right'))
button_layout.addWidget(self.right_file_btn)
self.compare_btn = QPushButton("比較")
self.compare_btn.clicked.connect(self.compare)
button_layout.addWidget(self.compare_btn)
main_layout.addLayout(button_layout)
# 主內(nèi)容區(qū)域
self.splitter = QSplitter(Qt.Horizontal)
# 左側文件樹
self.left_tree = QTreeWidget()
self.left_tree.setHeaderLabel("左側文件")
self.left_tree.itemClicked.connect(self.on_tree_item_clicked)
self.splitter.addWidget(self.left_tree)
# 右側文件樹
self.right_tree = QTreeWidget()
self.right_tree.setHeaderLabel("右側文件")
self.right_tree.itemClicked.connect(self.on_tree_item_clicked)
self.splitter.addWidget(self.right_tree)
# 底部文本比較區(qū)域
self.text_splitter = QSplitter(Qt.Horizontal)
self.left_text = QTextEdit()
self.left_text.setReadOnly(True)
self.left_text.setFont(QFont("Courier New", 10))
self.text_splitter.addWidget(self.left_text)
self.right_text = QTextEdit()
self.right_text.setReadOnly(True)
self.right_text.setFont(QFont("Courier New", 10))
self.text_splitter.addWidget(self.right_text)
# 垂直分割器
v_splitter = QSplitter(Qt.Vertical)
v_splitter.addWidget(self.splitter)
v_splitter.addWidget(self.text_splitter)
v_splitter.setSizes([400, 400])
main_layout.addWidget(v_splitter)
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
def select_file_or_dir(self, side):
options = QFileDialog.Options()
path, _ = QFileDialog.getOpenFileName(self, f"選擇{side}文件", "", "All Files (*);;Text Files (*.txt)", options=options)
if not path: # 如果用戶沒有選擇文件,嘗試選擇文件夾
path = QFileDialog.getExistingDirectory(self, f"選擇{side}文件夾")
if not path:
return
if side == 'left':
self.left_dir = path
self.populate_tree(self.left_tree, path)
else:
self.right_dir = path
self.populate_tree(self.right_tree, path)
def populate_tree(self, tree, path):
tree.clear()
if os.path.isfile(path):
item = QTreeWidgetItem([os.path.basename(path)])
item.setData(0, Qt.UserRole, path)
tree.addTopLevelItem(item)
elif os.path.isdir(path):
self.add_dir_to_tree(tree, path)
def add_dir_to_tree(self, tree, dir_path, parent=None):
try:
items = os.listdir(dir_path)
except PermissionError:
return
items.sort()
for item_name in items:
item_path = os.path.join(dir_path, item_name)
if parent is None:
tree_item = QTreeWidgetItem([item_name])
tree.addTopLevelItem(tree_item)
else:
tree_item = QTreeWidgetItem(parent, [item_name])
tree_item.setData(0, Qt.UserRole, item_path)
if os.path.isdir(item_path):
self.add_dir_to_tree(tree, item_path, tree_item)
def on_tree_item_clicked(self, item, column):
file_path = item.data(0, Qt.UserRole)
if os.path.isfile(file_path):
sender = self.sender()
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except UnicodeDecodeError:
content = "無法以文本形式顯示此文件"
if sender == self.left_tree:
self.left_text.setPlainText(content)
else:
self.right_text.setPlainText(content)
def compare(self):
if not self.left_dir or not self.right_dir:
QMessageBox.warning(self, "警告", "請先選擇左右兩側的文件或文件夾")
return
# 比較文件或文件夾
if os.path.isfile(self.left_dir) and os.path.isfile(self.right_dir):
self.compare_files(self.left_dir, self.right_dir)
elif os.path.isdir(self.left_dir) and os.path.isdir(self.right_dir):
self.compare_dirs(self.left_dir, self.right_dir)
else:
QMessageBox.warning(self, "警告", "請選擇相同類型的項目進行比較(文件與文件,文件夾與文件夾)")
def compare_files(self, file1, file2):
try:
with open(file1, 'r', encoding='utf-8') as f1, open(file2, 'r', encoding='utf-8') as f2:
lines1 = f1.readlines()
lines2 = f2.readlines()
except UnicodeDecodeError:
QMessageBox.warning(self, "警告", "無法比較非文本文件")
return
self.left_text.setPlainText(''.join(lines1))
self.right_text.setPlainText(''.join(lines2))
# 高亮顯示差異
self.highlight_differences(lines1, lines2)
def highlight_differences(self, lines1, lines2):
# 清除之前的高亮
self.left_text.setExtraSelections([])
self.right_text.setExtraSelections([])
# 使用difflib找出差異
differ = difflib.SequenceMatcher(None, lines1, lines2)
opcodes = differ.get_opcodes()
left_selections = []
right_selections = []
for tag, i1, i2, j1, j2 in opcodes:
if tag == 'replace' or tag == 'delete':
# 左側文件的高亮
selection = QTextEdit.ExtraSelection()
selection.cursor = self.left_text.textCursor()
selection.cursor.setPosition(0)
start_pos = sum(len(line) for line in lines1[:i1])
end_pos = sum(len(line) for line in lines1[:i2])
selection.cursor.setPosition(start_pos)
selection.cursor.setPosition(end_pos, QTextCursor.KeepAnchor)
if tag == 'replace':
selection.format.setBackground(QColor(255, 255, 0)) # 黃色
else:
selection.format.setBackground(QColor(255, 200, 200)) # 淺紅色
left_selections.append(selection)
if tag == 'replace' or tag == 'insert':
# 右側文件的高亮
selection = QTextEdit.ExtraSelection()
selection.cursor = self.right_text.textCursor()
selection.cursor.setPosition(0)
start_pos = sum(len(line) for line in lines2[:j1])
end_pos = sum(len(line) for line in lines2[:j2])
selection.cursor.setPosition(start_pos)
selection.cursor.setPosition(end_pos, QTextCursor.KeepAnchor)
if tag == 'replace':
selection.format.setBackground(QColor(255, 255, 0)) # 黃色
else:
selection.format.setBackground(QColor(200, 255, 200)) # 淺綠色
right_selections.append(selection)
self.left_text.setExtraSelections(left_selections)
self.right_text.setExtraSelections(right_selections)
def compare_dirs(self, dir1, dir2):
self.left_tree.clear()
self.right_tree.clear()
self.populate_tree(self.left_tree, dir1)
self.populate_tree(self.right_tree, dir2)
comparison = filecmp.dircmp(dir1, dir2)
# 標記不同的文件
self.mark_different_files(comparison)
def mark_different_files(self, comparison):
# 標記左側獨有的文件為紅色
for item in self.get_all_items(self.left_tree):
rel_path = os.path.relpath(item.data(0, Qt.UserRole), self.left_dir)
if rel_path in comparison.left_only:
item.setForeground(0, QBrush(QColor(255, 0, 0)))
# 標記右側獨有的文件為綠色
for item in self.get_all_items(self.right_tree):
rel_path = os.path.relpath(item.data(0, Qt.UserRole), self.right_dir)
if rel_path in comparison.right_only:
item.setForeground(0, QBrush(QColor(0, 255, 0)))
# 標記內(nèi)容不同的文件為黃色
for item in self.get_all_items(self.left_tree):
rel_path = os.path.relpath(item.data(0, Qt.UserRole), self.left_dir)
if rel_path in comparison.diff_files:
item.setForeground(0, QBrush(QColor(255, 255, 0)))
# 遞歸處理子目錄
for sub_dir in comparison.subdirs.values():
self.mark_different_files(sub_dir)
def get_all_items(self, tree):
items = []
iterator = QTreeWidgetItemIterator(tree)
while iterator.value():
items.append(iterator.value())
iterator += 1
return items
if __name__ == "__main__":
app = QApplication(sys.argv)
window = FileCompareApp()
window.show()
sys.exit(app.exec_())功能說明
這個PyQt實現(xiàn)的Beyond Compare簡化版包含以下功能:
文件/文件夾選擇:
- 可以選擇左側和右側的文件或文件夾進行比較
- 支持通過文件對話框選擇
目錄比較:
顯示兩側目錄結構
用不同顏色標記差異:
- 紅色:僅在左側存在的文件
- 綠色:僅在右側存在的文件
- 黃色:兩側都存在但內(nèi)容不同的文件
文件內(nèi)容比較:
顯示文件內(nèi)容差異
高亮顯示不同之處:
- 黃色:被修改的內(nèi)容
- 淺紅色:被刪除的內(nèi)容
- 淺綠色:新增的內(nèi)容
用戶界面:
- 使用QSplitter實現(xiàn)可調(diào)整大小的分割窗口
- 左側和右側對稱布局,類似Beyond Compare
- 使用樹形控件顯示目錄結構
功能擴展迭代
要使這個工具更接近Beyond Compare,可以考慮添加以下功能:
- 二進制文件比較:添加十六進制查看器比較二進制文件
- 合并功能:允許用戶選擇將差異從一個文件復制到另一個文件
- 過濾選項:添加文件類型過濾和忽略特定文件/文件夾的選項
- 三向比較:支持三個文件或文件夾的比較
- 保存比較結果:將比較結果保存為報告
- 書簽功能:允許用戶標記重要的差異位置
- 同步功能:實現(xiàn)文件夾同步功能
這個實現(xiàn)提供了基本框架,你可以根據(jù)需要進一步擴展和完善功能。
以上就是Python進行文件比對的不同場景實現(xiàn)方法詳解的詳細內(nèi)容,更多關于Python文件比對的資料請關注腳本之家其它相關文章!
相關文章
Pycharm2020.1安裝無法啟動問題即設置中文插件的方法
這篇文章主要介紹了Pycharm2020.1安裝無法啟動問題即設置中文插件的操作方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-08-08
pandas取dataframe特定行列的實現(xiàn)方法
大家在使用Python進行數(shù)據(jù)分析時,經(jīng)常要使用到的一個數(shù)據(jù)結構就是pandas的DataFrame,本文介紹了pandas取dataframe特定行列的實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-05-05
python實現(xiàn)秒殺商品的微信自動提醒功能(代碼詳解)
這篇文章主要介紹了python實現(xiàn)秒殺商品的微信自動提醒功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
python如何寫入dbf文件內(nèi)容及創(chuàng)建dbf文件
這篇文章主要介紹了python如何寫入dbf文件內(nèi)容及創(chuàng)建dbf文件,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08
詳解如何利用tushare、pycharm和excel三者結合進行股票分析
這篇文章主要介紹了詳解如何利用tushare、pycharm和excel三者結合進行股票分析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04

