difflib-序列比较类(12) – Python语言(必读进阶学习教程)(参考资料)
该模块提供用于比较序列的类和函数。例如,它可以用于比较文件,并且可以生成各种格式的差异信息,包括 HTML 和上下文以及统一差异。要比较目录和文件,另请参阅filecmp
模块。
- class
difflib.
SequenceMatcher
- 这是一个灵活的类,用于比较任何类型的序列对,只要序列元素是可散列的。基本算法早于 Ratcliff 和 Obershelp 在 1980 年代后期以双曲线名称“格式塔模式匹配”发布的算法,并且比它更高级一些。这个想法是找到不包含“垃圾”元素的最长连续匹配子序列;这些“垃圾”元素在某种意义上是无趣的,例如空行或空格。(处理垃圾是 Ratcliff 和 Obershelp 算法的扩展。)然后将相同的想法递归地应用于匹配子序列左侧和右侧的序列片段。这不会产生最少的编辑序列,但会产生对人们“看起来正确”的匹配。
时序:基本的 Ratcliff-Obershelp 算法在最坏情况下是三次时间,在预期情况下是二次时间。
SequenceMatcher
是最坏情况的二次时间,并且预期情况的行为以复杂的方式取决于序列有多少共同元素;最佳情况时间是线性的。自动垃圾启发式:
SequenceMatcher
支持自动将某些序列项视为垃圾的启发式。启发式计算每个单独项目在序列中出现的次数。如果一个项目的重复项(在第一个之后)占序列的 1% 以上,并且序列长度至少为 200 个项目,则该项目被标记为“流行”并被视为垃圾,以进行序列匹配。可以通过autojunk
在False
创建SequenceMatcher
.3.2 新版功能: autojunk参数。
- class
difflib.
Differ
- 这是一个用于比较文本行序列并产生人类可读差异或增量的类。Differ
SequenceMatcher
既用于比较行序列,也用于比较相似(接近匹配)行中的字符序列。delta的每一行都
Differ
以两个字母的代码开头:
-
码 含义 '- '
序列1独有的行 '+ '
序列2独有的行 ' '
两个序列共有的线 '? '
两个输入序列中都不存在该行 以’
?
‘ 开头的行试图引导眼睛注意到内部差异,并且在任一输入序列中都不存在。如果序列包含制表符,这些行可能会造成混淆。
- class
difflib.
HtmlDiff
- 此类可用于创建HTML表(或包含表的完整HTML文件),并逐行显示文本与行间和行内更改突出显示的逐行比较。该表可以在完全或上下文差异模式下生成。
这个类的构造函数是:
tabsize=8__init__(
wrapcolumn=None,
linejunk=None,
charjunk=IS_CHARACTER_JUNK,
)
- 初始化实例
HtmlDiff
。tabsize是一个可选的关键字参数,用于指定制表位间距和默认值
8
。wrapcolumn是一个可选的关键字,用于指定拆分和包装行的列号,默认为
None
未包装行的位置。linejunk和charjunk是传入的可选关键字参数
ndiff()
(用于HtmlDiff
生成并排的HTML差异)。有关ndiff()
参数默认值和说明,请参阅 文档。
以下方法是公开的:
fromlinesmake_file(
tolines,
fromdesc=”,
todesc=”,
context=False,
numlines=5,
*,
charset=’utf-8′,
)
- 比较fromlines和tolines(字符串列表)并返回一个字符串,该字符串是一个完整的HTML文件,其中包含一个表格,显示逐行差异,突出显示行间和行内更改。
fromdesc和todesc是可选的关键字参数,用于指定从/到文件列标题字符串(默认为空字符串)。
context和numlines都是可选的关键字参数。设置方面,以
True
当背景的差异将被显示,否则默认为False
可显示完整的文件。numlines默认为5
。当上下文 为True
numlines时,控制围绕差异突出显示的上下文行数。当上下文为False
numlines时,控制使用“下一个”超链接时差异高亮显示之前显示的行数(设置为零会导致“下一个”超链接将下一个差异高亮显示在浏览器顶部而没有任何前导上下文)。版本3.5中已更改:添加了charset仅关键字参数。HTML文档的默认字符集从更改
'ISO-8859-1'
为'utf-8'
。
fromlinesmake_table(
tolines,
fromdesc=”,
todesc=”,
context=False,
numlines=5,
)
- 比较fromlines和tolines(字符串列表)并返回一个字符串,该字符串是一个完整的HTML表格,显示逐行差异,突出显示行间和行内更改。
此方法的参数与方法的参数相同
make_file()
。
Tools/scripts/diff.py
是这个类的命令行前端,并包含一个很好的使用示例。
difflib.
acontext_diff(
b,
fromfile=”,
tofile=”,
fromfiledate=”,
tofiledate=”,
n=3,
lineterm=’\n’,
)
- 比较a和b(字符串列表); 以上下文diff格式返回delta(生成delta行的生成器)。
上下文差异是一种紧凑的方式,只显示已更改的行加上几行上下文。更改以前/后样式显示。上下文行的数量由n设置,默认为3。
默认情况下,diff控制线(带
***
或的那些---
)是使用尾随换行符创建的。这是有帮助的,因此由于输入和输出都具有尾随换行符io.IOBase.readlines()
,因此io.IOBase.writelines()
从输入和输出中产生的输入创建的输入 适合使用 。对于没有尾随换行符的输入,请将lineterm参数设置为以
""
使输出统一为newline free。上下文差异格式通常具有文件名和修改时间的标头。可以使用fromfile, tofile,fromfiledate和tofiledate的字符串指定任何或所有这些。修改时间通常以ISO 8601格式表示。如果未指定,则字符串默认为空白。
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n'] >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n'] >>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py', tofile='after.py')) *** before.py --- after.py *************** *** 1,4 **** ! bacon ! eggs ! ham guido --- 1,4 ---- ! python ! eggy ! hamster guido
有关更详细的示例,请参阅difflib的命令行界面。
difflib.
wordget_close_matches(
possibilities,
n=3,
cutoff=0.6,
)
- 返回最佳“足够好”的比赛列表。 word是需要密切匹配的序列(通常是字符串),并且可能性是与字匹配的序列列表(通常是字符串列表)。
可选参数n(默认值
3
)是要返回的最大匹配数; n必须大于0
。可选参数cutoff(默认值
0.6
)是[0,1]范围内的浮点数。不会得到至少与单词类似的得分的可能性被忽略。可能性中的最佳(不超过n)匹配在列表中返回,按相似性得分排序,最相似。
>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy']) ['apple', 'ape'] >>> import keyword >>> get_close_matches('wheel', keyword.kwlist) ['while'] >>> get_close_matches('pineapple', keyword.kwlist) [] >>> get_close_matches('accept', keyword.kwlist) ['except']
difflib.
andiff(
b,
linejunk=None,
charjunk=IS_CHARACTER_JUNK,
)
- 比较a和b(字符串列表); 返回a-
Differ
style delta(生成三角形线的生成器)。可选的关键字参数linejunk和charjunk是过滤功能(或
None
):linejunk:接受单个字符串参数的函数,如果字符串是垃圾则返回true,否则返回false。默认是
None
。还有一个模块级函数IS_LINE_JUNK()
,它过滤掉没有可见字符的行,除了最多一磅字符('#'
) – 但是底层SequenceMatcher
类对动态分析哪些行频繁构成噪声,这通常有效比使用这个功能更好。charjunk:接受字符(长度为1的字符串)的函数,如果字符是垃圾则返回,否则返回false。默认是模块级函数
IS_CHARACTER_JUNK()
,它过滤掉空格字符(空格或制表符;在此包含换行符是个坏主意!)。Tools/scripts/ndiff.py
是此函数的命令行前端。>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) >>> print(''.join(diff), end="") - one ? ^ + ore ? ^ - two - three ? - + tree + emu
difflib.
sequencerestore(
which,
)
- 返回生成delta的两个序列之一。
给定一个序列通过产生
Differ.compare()
或ndiff()
从文件1或2(参数始发,提取线其中),剥离线前缀。例:
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) >>> diff = list(diff) # materialize the generated delta into a list >>> print(''.join(restore(diff, 1)), end="") one two three >>> print(''.join(restore(diff, 2)), end="") ore tree emu
difflib.
aunified_diff(
b,
fromfile=”,
tofile=”,
fromfiledate=”,
tofiledate=”,
n=3,
lineterm=’\n’,
)
- 比较a和b(字符串列表); 以统一的diff格式返回delta(生成delta行的生成器)。
统一差异是一种紧凑的方式,只显示已更改的行加上几行上下文。更改以内联样式显示(而不是在块之前/之后单独)。上下文行的数量由n设置,默认为3。
默认情况下,差异控制线(那些
---
,+++
或@@
)与尾部换行符创建。这是有帮助的,因此由于输入和输出都具有尾随换行符io.IOBase.readlines()
,因此io.IOBase.writelines()
从输入和输出中产生的输入创建的输入适合使用 。对于没有尾随换行符的输入,请将lineterm参数设置为以
""
使输出统一为newline free。上下文差异格式通常具有文件名和修改时间的标头。可以使用fromfile, tofile,fromfiledate和tofiledate的字符串指定任何或所有这些。修改时间通常以ISO 8601格式表示。如果未指定,则字符串默认为空白。
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n'] >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n'] >>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py')) --- before.py +++ after.py @@ -1,4 +1,4 @@ -bacon -eggs -ham +python +eggy +hamster guido
有关更详细的示例,请参阅difflib的命令行界面。
difflib.
dfuncdiff_bytes(
a,
b,
fromfile=b”,
tofile=b”,
fromfiledate=b”,
tofiledate=b”,
n=3,
lineterm=b’\n’,
)
- 使用dfunc比较a和b(字节对象列表); 以dfunc返回的格式生成一系列delta行(也是字节)。 dfunc必须是可调用的,通常是或者 。
unified_diff()
context_diff()
允许您比较具有未知或不一致编码的数据。除n之外的所有输入必须是字节对象,而不是str。通过无损地将所有输入(除了n)转换为str和调用来工作。然后将dfunc的输出 转换回字节,因此您收到的delta行与a和b具有相同的未知/不一致编码。
dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)
版本3.5中的新功能。
difflib.
lineIS_LINE_JUNK(
)
- 对于可忽略的行返回true。线线是可忽略的,如果线为空或包含一个单一的
'#'
,否则就不是忽略。作为参数的默认linejunk在ndiff()
旧版本中。
difflib.
chIS_CHARACTER_JUNK(
)
- 对可忽略的字符返回 True。 如果 ch 是空格或制表符,则字符 ch 可忽略,否则不可忽略。 在 ndiff() 中用作参数 charjunk 的默认值。
SequenceMatcher对象
该SequenceMatcher
班有这样的构造函数:
- class
difflib.
isjunk=NoneSequenceMatcher(
a=”,
b=”,
autojunk=True,
)
- 可选参数isjunk必须是
None
(缺省值)或带有sequence元素的单参数函数,当且仅当元素是“垃圾”并且应该被忽略时才返回true。通过None
对isjunk相当于通过; 换句话说,没有元素被忽略。例如,通过:lambda x: 0
lambda x: x in " \t"
如果你将线条比较为字符序列,并且不想在空白或硬标签上进行同步。
可选参数a和b是要比较的序列; 两者都默认为空字符串。两个序列的元素必须是可清除的。
可选参数autojunk可用于禁用自动垃圾启发式。
新版本3.2:在autojunk参数。
SequenceMatcher对象获得三个数据属性:bjunk是b的元素集,isjunk是
True
; bpopular是启发式考虑的非垃圾元素集(如果它没有被禁用); b2j是一个字典,它将b的剩余元素映射到它们出现的位置列表。每当用或复位b时,所有三个都复位。set_seqs()
set_seq2()
新版本3.2:在bjunk和bpopular属性。
SequenceMatcher
对象具有以下方法:set_seqs
(a,b )- 设置要比较的两个序列。
SequenceMatcher
计算和缓存有关第二个序列的详细信息,因此如果要将一个序列与多个序列进行比较,请使用set_seq2()
一次设置常用序列并set_seq1()
重复调用,每个其他序列一次。set_seq1
(a )- 设置要比较的第一个序列。要比较的第二个序列没有改变。
set_seq2
(b )- 设置要比较的第二个序列。要比较的第一个序列没有改变。
alofind_longest_match(
ahi,
blo,
bhi,
)
- 在
a[alo:ahi]
和中找到最长的匹配块b[blo:bhi]
。如果isjunk省略或
None
,find_longest_match()
返回 ,使得等于,其中和。对于所有符合这些条件的,额外的条件,和如果,也达到了。换言之,所有最大匹配块,返回一个启动在最早一个,以及所有开始最早的那些最大匹配块的一个,返回在开始最早的一个b。(i, j, k)
a[i:i+k]
b[j:j+k]
alo <= i <= i+k <=ahi
blo <= j <= j+k <= bhi
(i', j', k')
k >= k'
i <= i'
i == i'
j <= j'
>>> s = SequenceMatcher(None, " abcd", "abcd abcd") >>> s.find_longest_match(0, 5, 0, 9) Match(a=0, b=4, size=5)
如果提供了isjunk,则首先确定最长匹配块,如上所述,但是具有额外限制,即块中不出现垃圾元素。然后通过匹配(仅)两侧的垃圾元素尽可能地扩展该块。因此,结果块永远不会在垃圾上匹配,除非相同的垃圾恰好与有趣的匹配相邻。
这是与以前相同的例子,但考虑到空白是垃圾。这可以防止直接匹配第二序列的尾端。相反,只有can匹配,并匹配第二个序列中最左边的:
' abcd'
' abcd'
'abcd'
'abcd'
>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd") >>> s.find_longest_match(0, 5, 0, 9) Match(a=1, b=0, size=4)
如果没有匹配的块,则返回
(alo, blo, 0)
此方法返回一个命名元组
Match(a, b, size)
get_matching_blocks()
- 返回描述非重叠匹配子序列的三元组列表。每个三元组都是形式,意思是。三元组在i和j中单调递增。
(i,j, n)
a[i:i+n] == b[j:j+n]
最后一个三元组是一个虚拟,并具有该值。这是唯一的三倍。如果 列表中是和相邻的三元组,而第二个不是列表中的最后三元组,那么或; 换句话说,相邻的三元组总是描述不相邻的相等的块。
(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()
- 返回5元组的返回列表,描述如何将a转换为b。每个元组都是这种形式。第一个元组有,并且剩余元组的i1等于前一个元组的i2,同样,j1等于前一个j2。
(tag, i1, i2, j1, j2)
i1 == j1 == 0
该标签值是字符串,这些含义:
值 含义 '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'
n=3get_grouped_opcodes(
)
- 返回具有多达n行上下文的组的生成器。
从返回的组开始
get_opcodes()
,此方法拆分较小的更改簇并消除没有更改的中间范围。这些组的格式与
get_opcodes()
。
ratio()
- 将序列相似性的度量返回为[0,1]范围内的浮点数。
其中T是两个序列中元素的总数,M是匹配数,这是2.0 * M / T.注意,
1.0
如果序列相同,并且0.0
它们没有任何共同点。如果
get_matching_blocks()
或者get_opcodes()
尚未调用,则计算成本很高,在这种情况下,您可能想要尝试quick_ratio()
或real_quick_ratio()
首先获得上限。
quick_ratio()
ratio()
相对快速地返回上限。
real_quick_ratio()
- 快速返回上限
ratio()
。
由于不同的近似水平,返回匹配与总字符比率的三种方法可以给出不同的结果,尽管 quick_ratio()
并且real_quick_ratio()
始终至少与以下一样大 ratio()
:
>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0
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
生成的增量不会声称是最小 差异。相反,最小差异通常是违反直觉的,因为它们可能在任何可能的地方同步,有时相距100页。将同步点限制为连续匹配保留了一些局部性概念,偶尔会产生较长的差异。
该Differ
班有这样的构造函数:
- class
difflib.
linejunk=NoneDiffer(
charjunk=None,
)
- 可选关键字参数linejunk和charjunk用于过滤函数(或
None
):linejunk:接受单个字符串参数的函数,如果字符串是垃圾,则返回true。默认值是
None
,意味着没有行被视为垃圾。charjunk:接受单个字符参数(长度为1的字符串)的函数,如果字符是垃圾,则返回true。默认值是
None
,意味着没有字符被视为垃圾。这些垃圾过滤功能可加快匹配以查找差异,并且不会导致忽略任何不同的行或字符。阅读
find_longest_match()
方法的isjunk 参数的说明以获得解释。Differ
通过单个方法使用对象(生成的增量):
acompare(
b,
)
- 比较两个行序列,并生成delta(一系列行)。
每个序列必须包含以换行符结尾的单个单行字符串。可以从
readlines()
类文件对象的方法获得这样的序列 。生成的增量也包括换行符终止的字符串,可以通过类writelines()
文件对象的方法按原样打印。
不同的例子
此示例比较两个文本。首先,我们设置文本,以换行符结尾的单个单行字符串的序列(这样的序列也可以从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.
difflib的命令行界面
此示例显示如何使用difflib创建类似diff
实用程序。它也包含在Python源代码发行版中 Tools/scripts/diff.py
。
#!/usr/bin/env python3
""" Command line interface to difflib.py providing diffs in four formats:
* ndiff: lists every line and highlights interline changes.
* context: highlights clusters of changes in a before/after format.
* unified: highlights clusters of changes in an inline format.
* html: generates side by side comparison with change highlights.
"""
import sys, os, difflib, argparse
from datetime import datetime, timezone
def file_mtime(path):
t = datetime.fromtimestamp(os.stat(path).st_mtime, timezone.utc)
return t.astimezone().isoformat()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-c', action='store_true', default=False, help='Produce a context format diff (default)')
parser.add_argument('-u', action='store_true', default=False, help='Produce a unified format diff')
parser.add_argument('-m', action='store_true', default=False, help='Produce HTML side by side diff ' '(can use -c and -l in conjunction)')
parser.add_argument('-n', action='store_true', default=False, help='Produce a ndiff format diff')
parser.add_argument('-l', '--lines', type=int, default=3, help='Set number of context lines (default 3)')
parser.add_argument('fromfile')
parser.add_argument('tofile')
options = parser.parse_args()
n = options.lines
fromfile = options.fromfile
tofile = options.tofile
fromdate = file_mtime(fromfile)
todate = file_mtime(tofile)
with open(fromfile) as ff:
fromlines = ff.readlines()
with open(tofile) as tf:
tolines = tf.readlines()
if options.u:
diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
elif options.n:
diff = difflib.ndiff(fromlines, tolines)
elif options.m:
diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
else:
diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
sys.stdout.writelines(diff)
if __name__ == '__main__':
main()