ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

比较两文件差异python实现

2026/9/29 2:20:13 拓冰建站 浏览量
比较两文件差异python实现 比较1比较2下面的是GUI方式俩栏颜色对比较适合前后有改动的同一文档import sysfrom difflib import SequenceMatcherfrom PySide6.QtCore import Qt, QRegularExpressionfrom PySide6.QtGui import (QFont, QColor, QSyntaxHighlighter, QTextCharFormat,QTextCursor, QTextDocument)from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,QHBoxLayout, QPlainTextEdit, QPushButton, QFileDialog,QLabel, QSplitter)# ---------- Diff 计算 ----------def compute_diff(a_lines, b_lines):返回 b_lines 的标注列表: (equal|add|del|change, line, a_idx)sm SequenceMatcher(None, a_lines, b_lines, autojunkFalse)result []for tag, i1, i2, j1, j2 in sm.get_opcodes():if tag equal:for k in range(j2 - j1):result.append((equal, b_lines[j1 k], i1 k))elif tag insert:for k in range(j1, j2):result.append((add, b_lines[k], None))elif tag delete:for k in range(i1, i2):result.append((del, a_lines[k], k))elif tag replace:for k in range(j1, j2):result.append((change, b_lines[k], None))for k in range(i1, i2):result.append((del, a_lines[k], k))return result# ---------- 高亮器 ----------COLORS {equal: QColor(#F5F5F5),add: QColor(#C8E6C9), # 浅绿del: QColor(#FFCDD2), # 浅红change: QColor(#FFF59D), # 浅黄}class DiffHighlighter(QSyntaxHighlighter):def __init__(self, doc, marks):super().__init__(doc)self.marks marks # list[bool] —— 长度等于行数def highlightBlock(self, text):block_num self.currentBlock().blockNumber()if block_num len(self.marks) and self.marks[block_num]:fmt QTextCharFormat()fmt.setBackground(QColor(#FFEB3B))fmt.setProperty(QTextCharFormat.Property.FullWidthSelection, True)self.setFormat(0, len(text), fmt)# ---------- 主窗口 ----------class DiffWindow(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle(Python Diff Viewer)self.resize(1200, 700)central QWidget(); self.setCentralWidget(central)v QVBoxLayout(central)# 顶部按钮栏bar QHBoxLayout()self.btn_left QPushButton( 打开左文件)self.btn_right QPushButton( 打开右文件)self.btn_diff QPushButton( 对比)for b in (self.btn_left, self.btn_right, self.btn_diff):bar.addWidget(b)bar.addStretch()v.addLayout(bar)# 左右文本框self.left QPlainTextEdit(); self.left.setFont(QFont(Consolas, 11))self.right QPlainTextEdit(); self.right.setFont(QFont(Consolas, 11))for ed in (self.left, self.right):ed.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)splitter QSplitter(Qt.Orientation.Horizontal)splitter.addWidget(self._wrap(左文件 (原版本), self.left))splitter.addWidget(self._wrap(右文件 (新版本), self.right))splitter.setSizes([600, 600])v.addWidget(splitter)# 信号self.btn_left.clicked.connect(lambda: self._open(self.left))self.btn_right.clicked.connect(lambda: self._open(self.right))self.btn_diff.clicked.connect(self._run_diff)def _wrap(self, title, edit):w QWidget(); lay QVBoxLayout(w); lay.setContentsMargins(0,0,0,0)lay.addWidget(QLabel(title))lay.addWidget(edit)return wdef _open(self, editor):path, _ QFileDialog.getOpenFileName(self, 选择文件)if path:with open(path, r, encodingutf-8, errorsignore) as f:editor.setPlainText(f.read())def _run_diff(self):a self.left.toPlainText().splitlines()b self.right.toPlainText().splitlines()diff compute_diff(a, b)# --- 左文件按 a 的顺序仅显示 del/equal ---left_marks []left_lines []# 同步高亮把 b 的 add/change 也映射到 a 侧对应行a_highlight set() # 在 a 中需要高亮的行号与 b 的 add/change 对齐# 重新跑一遍以同步两侧位置sm SequenceMatcher(None, a, b, autojunkFalse)a_pos, b_pos 0, 0a_blocks, b_blocks [], []for tag, i1, i2, j1, j2 in sm.get_opcodes():if tag equal:for k in range(i2 - i1):a_blocks.append((equal, a[i1k], i1k))b_blocks.append((equal, b[j1k], j1k))elif tag insert:for k in range(j1, j2):a_blocks.append((pad, , i1))b_blocks.append((add, b[k], j1k))elif tag delete:for k in range(i1, i2):a_blocks.append((del, a[k], i1k))b_blocks.append((pad, , j1))elif tag replace:# 先用 a 行标 del再用 b 行标 add/changefor k in range(i1, i2):a_blocks.append((del, a[k], i1k))b_blocks.append((pad, , j1k))for k in range(j1, j2):a_blocks.append((pad, , i1k))b_blocks.append((change, b[k], j1k))# 渲染到文本框 上色self._render(self.left, a_blocks)self._render(self.right, b_blocks)def _render(self, editor, blocks):editor.setPlainText(\n.join(b[1] for b in blocks))cursor editor.textCursor()cursor.select(QTextCursor.SelectionType.Document)fmt_default QTextCharFormat(); fmt_default.setBackground(QColor(white))cursor.setCharFormat(fmt_default)cursor.clearSelection()for i, (tag, _, _) in enumerate(blocks):color COLORS.get(tag)if not color: continueblock editor.document().findBlockByNumber(i)hl QTextCursor(block)hl.movePosition(QTextCursor.MoveOperation.EndOfBlock,QTextCursor.MoveMode.KeepAnchor)fmt QTextCharFormat(); fmt.setBackground(color)hl.setCharFormat(fmt)editor.viewport().update()if __name__ __main__:app QApplication(sys.argv)w DiffWindow(); w.show()sys.exit(app.exec())但缺行号显示补充