difflib无法正确找到操作码

时间:2018-10-15 21:10:29

标签: python difflib

我在python的difflib库中遇到一个非常奇怪的问题。我有两个字符串,如下所示,我对它们运行get_opcodes

import difflib

str1 = "MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(0), Integer(9))), Mul(Float('1.0', precision=24), MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(0), Integer(10))))"
str2 = "MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(1), Integer(9))), Mul(Float('1.0', precision=24), MatrixElement(MatrixSymbol('Btd', Integer(11), Integer(11)), Integer(1), Integer(10))))"
difflib.SequenceMatcher(None, str1,str2).get_opcodes()

仅在此特定示例中,diff的输出类似于以下内容,这显然是错误的。

[('equal', 0, 69, 0, 69),
 ('replace', 69, 70, 69, 70),
 ('equal', 70, 188, 70, 188),
 ('insert', 188, 188, 188, 201),
 ('equal', 188, 190, 201, 203),
 ('replace', 190, 206, 203, 206)]

正确的输出不应包含insert操作码,因为没有添加任何新内容。

这可能是difflib中的错误吗?

1 个答案:

答案 0 :(得分:2)

这不是错误。有多种方法可以将一个序列转换为另一个序列,这里的difflib输出是正确的。

尽管,您想知道为什么difflib选择了那个奇数变换而不是那个奇数变换:

[('equal', 0, 69, 0, 69),
 ('replace', 69, 70, 69, 70),
 ('equal', 70, 188, 70, 188),
 ('replace', 188, 189, 188, 189),
 ('equal', 189, 206, 189, 206)]

这归结为一件事:autojunk=True

准备学习junk

生成操作码的主要算法来自SequenceMatcher.get_matching_blocks,该方法将提供的序列分解为匹配的子序列。

要高效地执行此操作,它首先解析str2并构建一个dict,其中键是序列的字符,而值是相应字符的索引列表。

尽管,这可能会占用大量内存,因此,difflib.SequenceMatcher默认情况下会将某些可重复使用的字符视为垃圾,而不存储其索引。

来自difflib doc

  

自动垃圾启发式:[...]如果某项重复项(在第一个项之后)占序列和序列的1%以上   至少有200个项目,该项目被标记为“受欢迎”,并且   出于序列匹配的目的被视为垃圾。 [...]

在您的特定情况下,罪魁祸首是被视为垃圾的字符(SequenceMatcher对象无法看到从索引189开始的匹配序列,因为它是(

处理垃圾

获取预期输出的最简单方法是设置autojunk=False

difflib.SequenceMatcher(None, str1, str2, autojunk=False).get_opcodes()

这将输出您期望的结果:

[('equal', 0, 69, 0, 69),
 ('replace', 69, 70, 69, 70),
 ('equal', 70, 188, 70, 188),
 ('replace', 188, 189, 188, 189),
 ('equal', 189, 206, 189, 206)]

不过,请注意,有时完全关闭autojunk可能不是最佳选择,因为这可能会消耗更多的内存和时间。更好的方法是指定什么是垃圾邮件。

  

[...]这些“垃圾”元素在某种意义上是无趣的,例如空白行或空格[...]

当您使用difflib.ratio获取序列之间的相似性度量时,尤其如此。在这种情况下,您可能想忽略空格,因为它们通常在文本比较方面没有意义。

因此,如果您关闭autojunk,您仍然可以提供isjunk函数,该函数指示忽略空白。此参数是您在示例中设置为None的参数。

import difflib
from string import whitespace

...

difflib.SequenceMatcher(lambda x: x in whitespace, str1, str2, autojunk=False)