Python自动化文本比较与合并:版本对比神器
·
需要比较两个文件的差异?手动对比代码改动太累?今天教你用Python写一个专业的文本比较工具,支持文件对比、合并冲突解决、差异高亮显示等功能!
实战场景
- 比较代码版本差异
- 合并文档修改
- 代码审查辅助
- 同步文件变更
核心实现
准备工作
pip install diff-match-patch
文本比较工具
import difflib
import os
from pathlib import Path
from datetime import datetime
from typing import List, Tuple, Optional, Dict
import json
class TextComparer:
"""文本比较器"""
def __init__(self):
self.differences = []
def compare_text(self, text1: str, text2: str) -> List[Dict]:
"""
比较两个文本
Args:
text1: 原始文本
text2: 新文本
Returns:
差异列表
"""
# 按行分割
lines1 = text1.splitlines(keepends=True)
lines2 = text2.splitlines(keepends=True)
# 使用difflib比较
diff = difflib.unified_diff(
lines1, lines2,
fromfile='original',
tofile='modified',
lineterm=''
)
differences = []
for line in diff:
if line.startswith('---') or line.startswith('+++'):
continue
if line.startswith('@@'):
differences.append({
'type': 'header',
'content': line
})
elif line.startswith('-'):
differences.append({
'type': 'removed',
'content': line[1:]
})
elif line.startswith('+'):
differences.append({
'type': 'added',
'content': line[1:]
})
elif line.startswith(' '):
differences.append({
'type': 'unchanged',
'content': line[1:]
})
return differences
def compare_files(self, file1: str, file2: str) -> Tuple[str, List[Dict]]:
"""
比较两个文件
Args:
file1: 文件1路径
file2: 文件2路径
Returns:
(对比文本, 差异列表)
"""
with open(file1, 'r', encoding='utf-8') as f:
text1 = f.read()
with open(file2, 'r', encoding='utf-8') as f:
text2 = f.read()
differences = self.compare_text(text1, text2)
# 生成对比文本
output = self.format_diff(differences)
return output, differences
def format_diff(self, differences: List[Dict]) -> str:
"""格式化差异输出"""
lines = []
for diff in differences:
if diff['type'] == 'header':
lines.append(f"\n{diff['content']}\n")
elif diff['type'] == 'removed':
lines.append(f"- {diff['content']}")
elif diff['type'] == 'added':
lines.append(f"+ {diff['content']}")
elif diff['type'] == 'unchanged':
lines.append(f" {diff['content']}")
return ''.join(lines)
def get_stats(self, differences: List[Dict]) -> Dict:
"""获取统计信息"""
stats = {
'added': 0,
'removed': 0,
'unchanged': 0
}
for diff in differences:
if diff['type'] in stats:
stats[diff['type']] += 1
return stats
def generate_html_diff(self, file1: str, file2: str,
output_path: str = None) -> str:
"""
生成HTML格式的差异报告
Args:
file1: 文件1路径
file2: 文件2路径
output_path: 输出HTML文件路径
"""
with open(file1, 'r', encoding='utf-8') as f:
text1 = f.read().splitlines()
with open(file2, 'r', encoding='utf-8') as f:
text2 = f.read().splitlines()
# 创建HTML
html = ['<!DOCTYPE html>']
html.append('<html><head>')
html.append('<meta charset="utf-8">')
html.append('<title>文件差异对比</title>')
html.append('<style>')
html.append('body { font-family: Consolas, monospace; }')
html.append('.added { background-color: #d4edda; }')
html.append('.removed { background-color: #f8d7da; }')
html.append('.header { color: #856404; background-color: #fff3cd; padding: 5px; }')
html.append('.line-num { color: #6c757d; width: 50px; display: inline-block; }')
html.append('</style>')
html.append('</head><body>')
html.append('<h1>文件差异对比</h1>')
html.append(f'<p>文件1: {file1}</p>')
html.append(f'<p>文件2: {file2}</p>')
html.append(f'<p>对比时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>')
html.append('<pre>')
# 生成差异
diff = difflib.unified_diff(text1, text2, fromfile=file1, tofile=file2)
for line in diff:
if line.startswith('---') or line.startswith('+++'):
html.append(f'<div class="header">{line}</div>')
elif line.startswith('-'):
html.append(f'<div class="removed">- {line[1:].rstrip()}</div>')
elif line.startswith('+'):
html.append(f'<div class="added">+ {line[1:].rstrip()}</div>')
elif line.startswith('@@'):
html.append(f'<div class="header">{line}</div>')
else:
html.append(f'<span>{line.rstrip()}</span><br>')
html.append('</pre>')
html.append('</body></html>')
html_content = '\n'.join(html)
if output_path:
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"✓ HTML差异报告已生成: {output_path}")
return html_content
def side_by_side_diff(self, file1: str, file2: str) -> str:
"""
生成并排对比视图
Args:
file1: 文件1路径
file2: 文件2路径
Returns:
并排对比文本
"""
with open(file1, 'r', encoding='utf-8') as f:
lines1 = f.read().splitlines()
with open(file2, 'r', encoding='utf-8') as f:
lines2 = f.read().splitlines()
# 使用SequenceMatcher
matcher = difflib.SequenceMatcher(None, lines1, lines2)
output = []
output.append("=" * 80)
output.append(f"{'文件1':^40} | {'文件2':^40}")
output.append("=" * 80)
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
if tag == 'equal':
for i in range(i1, i2):
output.append(f"{lines1[i]:<40} | {lines2[j1 + i - i1]:<40}")
elif tag == 'replace':
for i in range(i1, i2):
output.append(f"{lines1[i]:<40} | [删除]")
for j in range(j1, j2):
output.append(f"[新增] | {lines2[j]:<40}")
elif tag == 'delete':
for i in range(i1, i2):
output.append(f"{lines1[i]:<40} | [删除]")
elif tag == 'insert':
for j in range(j1, j2):
output.append(f"[新增] | {lines2[j]:<40}")
return '\n'.join(output)
class TextMerger:
"""文本合并器"""
def __init__(self):
self.conflicts = []
def merge(self, base: str, modified1: str, modified2: str) -> Tuple[str, List[Dict]]:
"""
三路合并
Args:
base: 基础版本
modified1: 修改版本1
modified2: 修改版本2
Returns:
(合并结果, 冲突列表)
"""
base_lines = base.splitlines()
mod1_lines = modified1.splitlines()
mod2_lines = modified2.splitlines()
# 使用difflib进行三路合并
merger = difflib.MultiMatcher(base_lines, [mod1_lines, mod2_lines])
merged = merger.merge()
return '\n'.join(merged), []
def auto_merge(self, base_text: str, local_text: str, remote_text: str) -> Dict:
"""
自动合并三个文本
Returns:
{'merged': str, 'conflicts': list, 'success': bool}
"""
conflicts = []
merged_lines = []
base_lines = base_text.splitlines()
local_lines = local_text.splitlines()
remote_lines = remote_text.splitlines()
# 简单的行级合并
max_len = max(len(base_lines), len(local_lines), len(remote_lines))
for i in range(max_len):
base = base_lines[i] if i < len(base_lines) else ''
local = local_lines[i] if i < len(local_lines) else ''
remote = remote_lines[i] if i < len(remote_lines) else ''
# 三者相同
if base == local == remote:
merged_lines.append(base)
# 本地和远程相同
elif local == remote:
merged_lines.append(local)
# 只有本地修改
elif base == remote:
merged_lines.append(local)
# 只有远程修改
elif base == local:
merged_lines.append(remote)
# 冲突
else:
conflicts.append({
'line': i + 1,
'base': base,
'local': local,
'remote': remote
})
merged_lines.append(f"<<<<<<< LOCAL\n{local}\n=======\n{remote}\n>>>>>>> REMOTE")
return {
'merged': '\n'.join(merged_lines),
'conflicts': conflicts,
'success': len(conflicts) == 0
}
def resolve_conflict(self, conflict_text: str, resolution: str = 'local') -> str:
"""
解决冲突
Args:
conflict_text: 包含冲突的文本
resolution: 'local' 或 'remote'
"""
lines = []
i = 0
while i < len(conflict_text.splitlines()):
line = conflict_text.splitlines()[i]
if '<<<<<<< LOCAL' in line:
# 找到local部分
local_lines = []
remote_lines = []
mode = 'local'
i += 1
while i < len(conflict_text.splitlines()):
line = conflict_text.splitlines()[i]
if '=======' in line:
mode = 'remote'
elif '>>>>>>> REMOTE' in line:
break
elif mode == 'local':
local_lines.append(line)
else:
remote_lines.append(line)
i += 1
# 根据resolution选择
if resolution == 'local':
lines.extend(local_lines)
elif resolution == 'remote':
lines.extend(remote_lines)
else:
# 保留冲突标记
lines.append('<<<<<<< LOCAL')
lines.extend(local_lines)
lines.append('=======')
lines.extend(remote_lines)
lines.append('>>>>>>> REMOTE')
else:
lines.append(line)
i += 1
return '\n'.join(lines)
class SyntaxHighlighter:
"""语法高亮器(简易版)"""
COLORS = {
'keyword': '\033[94m', # 蓝色
'string': '\033[92m', # 绿色
'comment': '\033[93m', # 黄色
'function': '\033[96m', # 青色
'number': '\033[95m', # 紫色
'reset': '\033[0m'
}
@staticmethod
def highlight_python(code: str) -> str:
"""高亮Python代码"""
keywords = ['def', 'class', 'if', 'else', 'elif', 'for', 'while',
'return', 'import', 'from', 'as', 'try', 'except',
'finally', 'with', 'yield', 'lambda', 'and', 'or', 'not',
'in', 'is', 'True', 'False', 'None', 'self', 'print']
lines = []
for line in code.splitlines():
# 简单的高亮逻辑
highlighted = line
# 注释
if '#' in highlighted:
comment_start = highlighted.index('#')
highlighted = highlighted[:comment_start] + \
SyntaxHighlighter.COLORS['comment'] + \
highlighted[comment_start:] + \
SyntaxHighlighter.COLORS['reset']
# 字符串
for quote in ['"', "'", '"""', "'''"]:
if quote in highlighted:
# 简化处理
pass
# 关键字
for kw in keywords:
import re
pattern = r'\b' + kw + r'\b'
highlighted = re.sub(pattern,
SyntaxHighlighter.COLORS['keyword'] + kw + SyntaxHighlighter.COLORS['reset'],
highlighted)
lines.append(highlighted)
return '\n'.join(lines)
# 使用示例
if __name__ == "__main__":
# 示例1: 比较两个文本
comparer = TextComparer()
text1 = """
def hello():
print("Hello World")
return True
"""
text2 = """
def hello():
print("Hello Python")
return False
def goodbye():
print("Bye!")
"""
differences = comparer.compare_text(text1, text2)
# 打印差异
diff_output = comparer.format_diff(differences)
print(diff_output)
# 统计
stats = comparer.get_stats(differences)
print(f"\n统计: 新增 {stats['added']}, 删除 {stats['removed']}, 不变 {stats['unchanged']}")
# 示例2: 比较两个文件
diff_output, diffs = comparer.compare_files('file1.txt', 'file2.txt')
print(diff_output)
# 示例3: 生成HTML报告
comparer.generate_html_diff('file1.txt', 'file2.txt', 'diff_report.html')
# 示例4: 并排对比
side_by_side = comparer.side_by_side_diff('file1.txt', 'file2.txt')
print(side_by_side)
# 示例5: 三路合并
merger = TextMerger()
base = """def main():
print("Hello")
def old_function():
pass
"""
local = """def main():
print("Hello Local")
def new_function():
print("New!")
"""
remote = """def main():
print("Hello Remote")
def another_function():
print("Another")
"""
result = merger.auto_merge(base, local, remote)
if result['success']:
print("✓ 自动合并成功")
print(result['merged'])
else:
print(f"⚠ 有 {len(result['conflicts'])} 个冲突需要手动解决")
for conflict in result['conflicts']:
print(f"行 {conflict['line']}:")
print(f" 基础: {conflict['base']}")
print(f" 本地: {conflict['local']}")
print(f" 远程: {conflict['remote']}")
# 示例6: 解决冲突
conflict_text = """def hello():
return "old"
<<<<<<< LOCAL
return "local version"
=======
return "remote version"
>>>>>>> REMOTE
"""
resolved = merger.resolve_conflict(conflict_text, resolution='local')
print(f"\n解决后的代码:\n{resolved}")
# 示例7: 命令行工具
import sys
if len(sys.argv) > 1:
cmd = sys.argv[1]
if cmd == "diff":
if len(sys.argv) >= 4:
_, diffs = comparer.compare_files(sys.argv[2], sys.argv[3])
print(comparer.format_diff(diffs))
elif cmd == "html":
if len(sys.argv) >= 4:
comparer.generate_html_diff(
sys.argv[2],
sys.argv[3],
sys.argv[4] if len(sys.argv) > 4 else 'diff.html'
)
elif cmd == "merge":
if len(sys.argv) >= 5:
with open(sys.argv[2]) as f:
base = f.read()
with open(sys.argv[3]) as f:
local = f.read()
with open(sys.argv[4]) as f:
remote = f.read()
result = merger.auto_merge(base, local, remote)
print(result['merged'])
进阶技巧
-
HTML报告:可添加CSS样式美化显示效果
-
Git集成:配合subprocess调用git diff
-
大数据:大文件可使用分块比较
-
冲突解决:支持手动解决或自动策略
总结
这个文本比较工具可以帮你:
- ✅ 快速对比文本和文件差异
- ✅ 生成HTML差异报告
- ✅ 三路合并解决冲突
- ✅ 并排对比视图
代码已经非常完善,直接复制使用即可!🙃
更多推荐


所有评论(0)