Python 内置模块之 difflib - 差异化比较

简介

Difflib 是 Python 编程语言中的一个内置模块,帮助我们进行差异化比较。

它能够生成文本或者 html 格式的差异化比较结果,如果需要比较目录的不同,可以使用 filecmp 模块。

Python Difflib 模块中最常用的类是 Differ 和 Sequence Matcher 类。还有一些其他辅助类和函数,可以用于更特定的操作。

class difflib.SequenceMatcher

Sequence Matcher 比较两个提供的字符串,并返回表示两个字符串之间相似性的数据。

可以用来比较任何类型片段的类,只要比较的片段是可 hash 的。他源于1980,s的“完形匹配算法”,并且进行了一系列的优化和改进。它由于原始的完形匹配算法,在最坏情况下有n的平方次运算,在最好情况下,具有线性的效率。

它具有自动垃圾启发式,可以将重复超过片段 1% 或者重复 200 次的字符作为垃圾来处理。可以通过将 autojunk 设置为 false 关闭该功能。

autojunk 参数新增于2.7.1版本。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
# importing the difflib library and SequenceMatcher class  
import difflib
from difflib import SequenceMatcher

# defining the strings
str_1 = "Welcome to javatiku"
str_2 = "Welcome to Python tutorial"

# using the SequenceMatcher() function
my_seq = SequenceMatcher(a = str_1, b = str_2)

# printing the result
print("Sequence Matched:", my_seq.ratio()) # Sequence Matched: 0.5106382978723404

class difflib.Differ

Differ 被认为是 SequenceMatcher 的反义词。它接受文本行并查找字符串之间的差异。但 Differ 类在使用增量时特殊之处,使其更有效且对人类更可读,以便发现差异。

例如,在比较两个字符串之间插入新字符时,在接收额外字符的行之前会出现’ + ‘。它用以下符号来表示不同

Code Meaning
‘- ‘ 仅在片段1中存在
‘+ ‘ 仅在片段2中存在
‘ ‘ 片段1和2中都存在
‘? ‘ 存在疑问的

标识为?需要你通过人工的方式仔细比较他们的不同,他们产生的原因是源于混乱的制表符

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# importing the difflib module and Differ class  
import difflib
from difflib import Differ

# defining the strings
str_1 = "They would like to order a soft drink"
str_2 = "They would like to order a corn pizza"

# using the splitlines() function
lines_str1 = str_1.splitlines() # 此函数允许我们将字符串逐行比较,而不是逐字符比较。
lines_str2 = str_2.splitlines()

# using the Differ() and compare() function
dif = difflib.Differ()
my_diff = dif.compare(lines_str1, lines_str2)

# printing the results
print("Difference between the Strings")
print('\n'.join(my_diff))

输出

1
2
3
4
5
- They would like to order a soft drink
? ^ ^^ ^^ ^^

+ They would like to order a corn pizza
?

class difflib.HtmlDiff

这个类用来创建一个html表格(或者包含html表格的文件)用来展示文件差异。他既可以进行全文本展示,也可以只展示上下文不同。

这个类的构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)

tabsize表示制表符代表的空格个数,默认为8
wrapcolumn,可选参数,用来设置多少个字符时自动换行,默认None,为None时表示不自动换行
linejunk 和 charjunk,可选参数,在ndiff()中使用

这个类的公共方法:

make_file(fromlines, tolines [, fromdesc][, todesc][, context][, numlines])
用来生成一个包含表格的html文件,其内容是用来展示差异。
fromlines 和tolines,用于比较的内容,格式为字符串组成的列表
fromdesc 和 todesc,可选参数,对应的fromlines,tolines的差异化文件的标题,默认为空字符串
context 和 numlines,可选参数,context 为True时,只显示差异的上下文,为false,显示全文,numlines默认为5,当context为True时,控制展示上下文的行数,当context为false时,控制不同差异的高亮之间移动时“next”的开始位置(如果设置为0,当移动懂顶端时,超链接会丢失引用地址)

make_table(fromlines, tolines [, fromdesc][, todesc][, context][, numlines])
这个方法和make_file用法一样,唯一的区别在于它只生成了一个html表格字符串

python 安装包的 Tools/scripts/diff.py 是关于他们使用的一个很好的例子,它可以用命令行来运行。

get_close_matches

接受参数并返回与目标字符串最接近的匹配项。

语法:

1
2
3
4
5
6
get_close_matches(target_word, list_of_possibilities, n = res_limit, cutoff)  

第一个参数是要定位的单词;我们希望方法返回相似性。
第二个参数可以是变量或指向字符串数组的变量的数组。
第三个参数允许用户定义要返回的输出数量的限制。
最后一个参数确定两个单词之间的相似度需要多大才能作为输出返回。

仅使用前两个参数,函数将根据默认切割值0.6(在0 - 1范围内)和默认结果限制3返回输出。

示例:

1
2
3
4
5
6
7
8
9
# importing the difflib module and get_close_matches method  
import difflib
from difflib import get_close_matches

# using the get_close_matches method
my_list = get_close_matches('mas', ['master', 'mask', 'duck', 'cow', 'mass', 'massive', 'python', 'butter'])

# printing the list
print("Matching words:", my_list) # Matching words: ['mass', 'mask', 'master']

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# importing the difflib module and get_close_matches method  
import difflib
from difflib import get_close_matches

# using the get_close_matches method
my_list = get_close_matches(
'mas',
['master', 'mask', 'duck', 'cow',
'mass', 'massive', 'python', 'butter'],
n = 4,
cutoff = 0.6 # 越接近1,约束就越严格
)

# printing the list
print("Matching words:", my_list) # Matching words: ['mass', 'mask', 'master', 'massive']

unified_diffcontext_diff

在 difflib 中有两个类,它们的工作方式相同:unified_diffcontext_diff 。它们之间的唯一主要区别是结果。

context_diff 类接受两个数据字符串,然后返回从第一个字符串中插入或删除的每个单词。

示例:

1
2
3
4
5
6
7
8
9
10
11
# importing the required modules  
import sys
import difflib
from difflib import unified_diff

# defining the string variables
str_1 = ['Mark\n', 'Henry\n', 'Richard\n', 'Stella\n', 'Robin\n', 'Employees\n']
str_2 = ['Arthur\n', 'Joseph\n', 'Stacey\n', 'Harry\n', 'Emma\n', 'Employees\n']

# using the unified_diff() function
sys.stdout.writelines(unified_diff(str_1, str_2))

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
+++ 
@@ -1,6 +1,6 @@
-Mark
-Henry
-Richard
-Stella
-Robin
+Arthur
+Joseph
+Stacey
+Harry
+Emma
Employees

我们可以观察到 unified_diff 返回被删除的单词前缀为-,返回添加的单词前缀为+。最后一个单词”Employees”在两个字符串中都没有前缀。

context_diff 类的工作方式与 unified_diff 类似。但是,它不是显示原始字符串中插入和删除了什么,而是通过返回带有 ! 前缀的已更改行来表示哪些行发生了更改。

示例:

1
2
3
4
5
6
7
8
9
10
11
# importing the required modules  
import sys
import difflib
from difflib import context_diff

# defining the string variables
str_1 = ['Mark\n', 'Henry\n', 'Richard\n', 'Stella\n', 'Robin\n', 'Employees\n']
str_2 = ['Arthur\n', 'Joseph\n', 'Stacey\n', 'Harry\n', 'Emma\n', 'Employees\n']

# using the context_diff() function
sys.stdout.writelines(context_diff(str_1, str_2))

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
*** 
---
***************
*** 1,6 ****
! Mark
! Henry
! Richard
! Stella
! Robin
Employees
--- 1,6 ----
! Arthur
! Joseph
! Stacey
! Harry
! Emma
Employees

difflib.ndiff

比较a与b(字符串列表),返回一个Differ-style 的差异结果

1
2
3
4
5
difflib.ndiff(a, b[, linejunk][, charjunk])

linejunk和charjunk都是用来匹配的方法
linejunk:接收一个字符串的方法,如果这个字符串被认定为垃圾,则返回 true,否则为 false,默认为None,他调用了IS_LINE_JUNK() 这个方法,这个方法存在 bug,他不能过滤掉’#’周围的不可见字符,2.3以后,对这个方法进行了动态分析,表现会比以前好些
charjunk:接受一个字符的方法,如果这个字符被认定为垃圾,则返回 true,否则为 false,它调用了 IS_CHARACTER_JUNK(), 他会自动过滤掉空白字符(所以,不要用空白字符或者制表符作为分隔符)

difflib.restore

返回一个由两个比对序列产生的结果

1
difflib.restore(sequence, which)

Reference


Python 内置模块之 difflib - 差异化比较
https://flepeng.github.io/021-Python-31-Python-标准库-Python-标准库之-difflib-差异化比较/
作者
Lepeng
发布于
2021年3月17日
许可协议