00265 difflib --- 计算差异的辅助工具


前言

此模块提供用于比较序列的类和函数。 例如,它可被用于比较文件,并可产生多种格式的不同文件差异信息,包括 HTML 和上下文以及统一的 diff 数据。 有关比较目录和文件,另请参阅 filecmp 模块。

Operating System: Ubuntu 22.04.4 LTS

参考文档

  1. difflib — 计算差异的辅助工具

SequenceMatcher 的示例

以下示例比较两个字符串,并将空格视为“垃圾”:

>>> s = SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")

ratio() 返回一个 [0, 1] 范围内的浮点数,用来衡量序列的相似度。 根据经验,ratio() 值超过 0.6 就意味着两个序列非常接近匹配:

>>> print(round(s.ratio(), 3))
0.866

如果您只对序列的匹配的位置感兴趣,则 get_matching_blocks() 就很方便:

>>> for block in s.get_matching_blocks():
...     print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements

请注意 get_matching_blocks() 返回的最后一个元组 (len(a), len(b), 0) 始终只用于占位,这也是元组的末尾元素(匹配的元素个数)为 0 的唯一情况。

如果你想要知道如何将第一个序列转成第二个序列,可以使用 get_opcodes():

>>> for opcode in s.get_opcodes():
...     print("%6s a[%d:%d] b[%d:%d]" % opcode)
 equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
 equal a[8:29] b[17:38]

参见

此模块中的 get_close_matches() 函数显示了如何基于 SequenceMatcher 构建简单的代码来执行有用的功能。

针对使用 SequenceMatcher 构建的小型应用程序的 简易版本控制方案

Differ 示例

此示例比较两段文本。 首先我们设置文本为以换行符结尾的单行字符串组成的序列(这样的序列也可以通过文件型对象的 readlines() 方法来获取):

>>> text1 = '''  1. Beautiful is better than ugly.
...   2. Explicit is better than implicit.
...   3. Simple is better than complex.
...   4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = '''  1. Beautiful is better than ugly.
...   3.   Simple is better than complex.
...   4. Complicated is better than complex.
...   5. Flat is better than nested.
... '''.splitlines(keepends=True)

接下来我们实例化一个 Differ 对象:

>>> d = Differ()

请注意在实例化 Differ 对象时我们可以传入函数来过滤掉“垃圾”行和字符。 详情参见 Differ() 构造器说明。

最后,我们比较两个序列:

>>> result = list(d.compare(text1, text2))

result 是一个字符串列表,让我们将其美化打印出来:

>>> from pprint import pprint
>>> pprint(result)
['    1. Beautiful is better than ugly.\n',
 '-   2. Explicit is better than implicit.\n',
 '-   3. Simple is better than complex.\n',
 '+   3.   Simple is better than complex.\n',
 '?     ++\n',
 '-   4. Complex is better than complicated.\n',
 '?            ^                     ---- ^\n',
 '+   4. Complicated is better than complex.\n',
 '?           ++++ ^                      ^\n',
 '+   5. Flat is better than nested.\n']

作为单独的多行字符串显示出来则是这样:

>>> import sys
>>> sys.stdout.writelines(result)
    1. Beautiful is better than ugly.
-   2. Explicit is better than implicit.
-   3. Simple is better than complex.
+   3.   Simple is better than complex.
?     ++
-   4. Complex is better than complicated.
?            ^                     ---- ^
+   4. Complicated is better than complex.
?           ++++ ^                      ^
+   5. Flat is better than nested.

SequenceMatcher 对象

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

可选参数 isjunk 必须为 None (默认值) 或为接受一个序列元素并当且仅当其为应忽略的“垃圾”元素时返回真值的单参数函数。 传入 None 作为 isjunk 的值就相当于传入 lambda x: False;也就是说不忽略任何值。

ratio()

返回一个取值范围 [0, 1] 的浮点数作为序列相似性度量。

其中 T 是两个序列中元素的总数量,M 是匹配的数量,即 2.0*M / T。 请注意如果两个序列完全相同则该值为 1.0,如果两者完全不同则为 0.0。

如果 get_matching_blocks() 或 get_opcodes() 尚未被调用则此方法运算消耗较大,在此情况下你可能需要先调用 quick_ratio() 或 real_quick_ratio() 来获取一个上界。

注意: ratio() 调用的结果可能会取决于参数的顺序。 例如:

>>>> SequenceMatcher(None, 'tide', 'diet').ratio()
>0.25
>>>> SequenceMatcher(None, 'diet', 'tide').ratio()
>0.5

get_matching_blocks()

返回描述非重叠匹配子序列的三元组列表。 每个三元组的形式为 (i, j, n),其含义为 a[i:i+n] == b[j:j+n]。 这些三元组按 i 和 j 单调递增排列。

最后一个三元组用于占位,其值为 (len(a), len(b), 0)。 它是唯一 n == 0 的三元组。 如果 (i, j, n) 和 (i’, j’, n’) 是在列表中相邻的三元组,且后者不是列表中的最后一个三元组,则 i+n < i’ 或 j+n < j’;换句话说,相邻的三元组总是描述非相邻的相等块。

>>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>>> s.get_matching_blocks()
>[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]

get_opcodes()

返回描述如何将 a 变为 b 的 5 元组列表,每个元组的形式为 (tag, i1, i2, j1, j2)。 在第一个元组中 i1 == j1 == 0,而在其余的元组中 i1 等于前一个元组的 i2,并且 j1 也等于前一个元组的 j2。

tag 值为字符串,其含义如下:

含意
‘replace’ a[i1:i2] 应由 b[j1:j2] 替换。
‘delete’ a[i1:i2] 应被删除。 请注意在此情况下 j1 == j2。
‘insert’ b[j1:j2] 应插入到 a[i1:i1]。 请注意在此情况下 i1 == i2。
‘equal’ a[i1:i2] == b[j1:j2] (两个子序列相同)。

例如:

>>>> a = "qabxcd"
>>>> b = "abycdf"
>>>> s = SequenceMatcher(None, a, b)
>>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
>...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
>...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
>delete    a[0:1] --> b[0:0]      'q' --> ''
>equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
>replace   a[3:4] --> b[2:3]      'x' --> 'y'
>equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
>insert    a[6:6] --> b[5:6]       '' --> 'f'

Differ 对象

这个类的作用是比较由文本行组成的序列,并产生可供人阅读的差异或增量信息。 Differ 统一使用 SequenceMatcher 来完成行序列的比较以及相似(接近匹配)行内部字符序列的比较。

Differ 增量的每一行均以双字母代码打头:

双字母代码 含意
‘- ‘ 行为序列 1 所独有
‘+ ‘ 行为序列 2 所独有
‘ ‘ 行在两序列中相同
‘? ‘ 行不存在于任一输入序列

以 ‘?’ 打头的行尝试将视线紖至行以外而不存在于任一输入序列的差异。 如果序列包含空白符,例如空格、制表或换行则这些行可能会令人感到迷惑。

结语

第二百六十五篇博文写完,开心!!!!

今天,也是充满希望的一天。


文章作者: LuYF-Lemon-love
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LuYF-Lemon-love !
  目录