difflib —计算增量的助手

源代码: Lib/difflib.py


此模块提供用于比较序列的类和Function。例如,它可以用于比较文件,并可以产生各种格式的差异信息,包括 HTML 和上下文以及统一的 diff。要比较目录和文件,另请参见filecmp模块。

  • 类别 difflib. SequenceMatcher
    • 这是用于比较任何类型的序列对的灵活类,只要序列元素为hashable即可。基本算法早于 1980 年代末由 Ratcliff 和 Obershelp 在双曲线名称“格式塔模式匹配”下发布的算法,并且比在以前的算法更新颖。这个想法是找到最长的不包含“垃圾”元素的连续匹配子序列。这些“垃圾”元素在某种意义上是无趣的,例如空白行或空白。 (处理垃圾是对 Ratcliff 和 Obershelp 算法的扩展.)然后,将相同的思想递归应用于匹配子序列左侧和右侧的片段。这不会产生最小的编辑序列,但是会产生对人们“看起来正确”的匹配。

时间: 基本的 Ratcliff-Obershelp 算法在最坏的情况下是三次时间,在预期的情况下是二次时间。 SequenceMatcher是最坏情况的二次时间,其预期情况的行为以复杂的方式取决于序列共有多少个元素;最好的情况是线性的。

自动垃圾启发式: SequenceMatcher支持一种将某些序列项自动视为垃圾的启发式。试探法计算每个单个项目出现在序列中的次数。如果某项的重复项(在第一个项之后)占序列的 1%以上,并且该序列的长度至少为 200,则该项目被标记为“受欢迎”,并且出于序列匹配的目的被视为垃圾。可以pass在创建SequenceMatcher时将autojunk参数设置为False来关闭此启发式。

版本 3.2 中的新Function:* autojunk *参数。

  • 类别 difflib. Differ
    • 这是一类用于比较文本行序列并产生人类可读的差异或增量的类。 Differ 使用SequenceMatcher来比较行序列,并比较相似(近匹配)行中的字符序列。

Differ delta 的每一行都以两个字母的代码开头:

CodeMeaning
'- '序列 1 特有的行
'+ '序列 2 特有的行
' '这两个序列共有的线
'? '这两个 Importing 序列中都不存在行

以“ ?”开头的行试图将眼睛引导至行内差异,但在任一 Importing 序列中均不存在。如果序列包含制表符,这些行可能会造成混淆。

  • 类别 difflib. HtmlDiff
    • 此类可用于创建 HTML 表(或包含该表的完整 HTML 文件),以显示并排,逐行比较带有行间和行内更改突出显示的文本。该表可以完全或上下文差异模式生成。

此类的构造函数为:

  • __init__(* tabsize = 8 wrapcolumn = None linejunk = None charjunk = IS_CHARACTER_JUNK *)
  • tabsize *是一个可选的关键字参数,用于指定制表位的间距,默认为8

  • wrapcolumn *是一个可选关键字,用于指定断行和换行的列号,默认为None(不换行)。

  • linejunk charjunk *是传递给ndiff()的可选关键字参数(由HtmlDiff使用以生成并排的 HTML 差异)。有关参数默认值和说明,请参见ndiff()文档。

以下是公开的方法:

  • make_file(* fromlines tolines fromdesc ='' todesc ='' context = False numlines = 5 **,* charset ='utf-8'*)
    • 比较* fromlines tolines *(字符串列表)并返回一个字符串,它是一个完整的 HTML 文件,其中包含一个表格,其中显示了逐行差异,并突出显示了行间和行内更改。
  • fromdesc todesc *是可选的关键字参数,用于指定从/到文件列标题字符串(均默认为空字符串)。

  • context numlines 都是可选的关键字参数。当要显示上下文差异时,将 context *设置为True,否则默认为False以显示完整文件。 * numlines 默认为5。当 context True时, numlines 控制围绕差异高亮显示的上下文行的数量。当 context False时, numlines *控制使用“下一个”超链接时在差异突出显示之前显示的行数(设置为零会导致“下一个”超链接将下一个差异突出显示放置在浏览器,没有任何领先的上下文)。

Note

  • fromdesc todesc *被解释为未转义的 HTML,在接收来自不受信任来源的 Importing 时应正确转义。

在版本 3.5 中更改:添加了* charset *仅关键字参数。 HTML 文档的默认字符集从'ISO-8859-1'更改为'utf-8'

  • make_table((* fromlines tolines fromdesc ='' todesc ='' context = False numlines = 5 *)
    • 比较* fromlines tolines *(字符串列表)并返回一个字符串,该字符串是一个完整的 HTML 表,显示逐行差异,并突出显示行间和行内更改。

此方法的参数与make_file()方法的参数相同。

Tools/scripts/diff.py是此类的命令行前端,并且包含一个很好的用法示例。

  • difflib. context_diff(* a b fromfile ='' tofile ='' fromfiledate ='' tofiledate ='' n = 3 lineterm =' n'*)
    • 比较* a b *(字符串列表);以上下文差异格式返回增量(生成增量行的generator)。

上下文差异是一种仅显示已更改的行以及上下文的几行的紧凑方式。更改以之前/之后的样式显示。上下文行的数量由* n *设置,默认为三。

默认情况下,差异控制线(带有***---的控制线)使用尾随换行符创建。这很有用,因为从io.IOBase.readlines()创建的 Importing 将导致差异适合与io.IOBase.writelines()一起使用,因为 Importing 和输出均具有尾随换行符。

对于没有尾随换行符的 Importing,请将* lineterm *参数设置为"",以使输出一致地没有换行符。

上下文差异格式通常具有文件名和修改时间的 Headers。可以使用* 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. get_close_matches(* word possbilities n = 3 cutoff = 0.6 *)
    • 返回最佳“足够好”的匹配项列表。 * word 是需要紧密匹配的序列(通常是字符串), possibilities 是要与之匹配 word *的序列列表(通常是字符串列表)。

可选参数* n *(默认值3)是要返回的最大匹配数; * n *必须大于0

可选参数* cutoff (默认为0.6)是[0,1]范围内的浮点数。得分不低于 word *的可能性将被忽略。

可能性中的最佳匹配(不超过* 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. ndiff(* a b linejunk = None charjunk = IS_CHARACTER_JUNK *)
    • 比较* a b *(字符串列表);返回Differ样式的增量(generator生成增量线)。

可选关键字参数* linejunk charjunk *是过滤Function(或None):

  • linejunk *:一个函数,它接受单个字符串参数,如果字符串为垃圾,则返回 true,否则返回 false。默认值为None。还有一个模块级FunctionIS_LINE_JUNK(),过滤掉不带可见字符的行,但最多不超过一磅字符('#')–但是底层的SequenceMatcher类对哪些行是如此频繁以至于构成噪声进行了动态分析,并且这通常比使用此Function更好。

  • charjunk *:一个函数,该函数接受一个字符(长度为 1 的字符串),并在字符为垃圾字符时返回,否则返回 false。默认值为模块级别的函数IS_CHARACTER_JUNK(),该函数过滤出空格字符(空格或制表符;在其中包括换行符是个坏主意!)。

Tools/scripts/ndiff.py是此Function的命令行前端。

>>> 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. restore(序列其中)
    • 返回生成增量的两个序列之一。

给定Differ.compare()ndiff()产生的* sequence ,提取源自文件 1 或 2 的行(参数 which *),去除行前缀。

Example:

>>> 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. unified_diff(* a b fromfile ='' tofile ='' fromfiledate ='' tofiledate ='' n = 3 lineterm =' n'*)
    • 比较* a b *(字符串列表);以统一的 diff 格式返回增量(生成增量行的generator)。

统一差异是仅显示已更改的行以及上下文的几行的紧凑方式。更改以内联样式显示(而不是单独的 before/after 块)。上下文行的数量由* n *设置,默认为三。

默认情况下,差异控制线(带有---+++@@的控制线)以尾随换行符创建。这很有用,因为从io.IOBase.readlines()创建的 Importing 将导致差异适合与io.IOBase.writelines()一起使用,因为 Importing 和输出均具有尾随换行符。

对于没有尾随换行符的 Importing,请将* lineterm *参数设置为"",以使输出一致地没有换行符。

上下文差异格式通常具有文件名和修改时间的 Headers。可以使用* 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. diff_bytes(* dfunc a b fromfile = b'' tofile = b'' fromfiledate = b'' tofiledate = b'' n = 3 lineterm = b' n'*)
    • 使用* dfunc 比较 a b (字节对象列表);以 dfunc *返回的格式产生一系列增量线(也为字节)。 * dfunc *必须是可调用的,通常是unified_diff()context_diff()

使您可以比较编码未知或不一致的数据。除* n 外的所有 Importing 都必须是字节对象,而不是 str。pass将所有 Importing( n 除外)无损转换为 str 并调用dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)来工作。然后, dfunc 的输出将转换回字节,因此您收到的增量行与 a b *具有相同的未知/不一致编码。

3.5 版中的新Function。

  • difflib. IS_LINE_JUNK(* line *)

    • 返回True表示可忽略的行。如果* line 为空白或包含单个'#',则 line 行是可忽略的,否则不可忽略。用作旧版本ndiff()中参数 linejunk *的默认值。
  • difflib. IS_CHARACTER_JUNK(* ch *)

    • 返回True以获取可忽略字符。如果* ch 是空格或制表符,则字符 ch 是可忽略的,否则不可忽略。用作ndiff()中参数 charjunk *的默认值。

See also

SequenceMatcher Objects

SequenceMatcher类具有以下构造函数:

    • class * difflib. SequenceMatcher(* isjunk = None a ='' b ='' autojunk = True *)
    • 可选参数* isjunk 必须为None(默认值),或者是一个带参数的元素的单参数函数,当且仅当该元素为“垃圾”且应将其忽略时才返回 true。为 isjunk *传递None等效于lambda x: False传递;换句话说,不会忽略任何元素。例如,传递:
lambda x: x in " \t"

如果要将行作为字符序列进行比较,并且不想在空格或硬标签上同步。

可选参数* a b *是要比较的序列;两者都默认为空字符串。两个序列的元素必须为hashable

可选参数* autojunk *可用于禁用自动垃圾启发式。

版本 3.2 中的新Function:* autojunk *参数。

SequenceMatcher 对象获得三个数据属性:* bjunk b 的元素集,其中 isjunk *为True; * bpopular *是启发式方法(如果未禁用)认为流行的非垃圾元素集; * b2j 是将 b 其余元素 Map 到它们出现的位置列表的字典。每当 b *用set_seqs()set_seq2()重置时,这三个都将重置。

版本 3.2 中的新Function:* bjunk bpopular *属性。

SequenceMatcher对象具有以下方法:

  • set_seqs(* a b *)
    • 设置两个要比较的序列。

SequenceMatcher计算并缓存有关第二个序列的详细信息,因此,如果要将一个序列与多个序列进行比较,请使用set_seq2()一次设置常用序列,然后针对每个其他序列重复调用set_seq1()

  • set_seq1(* a *)

    • 设置要比较的第一个序列。要比较的第二个 Sequences 不变。
  • set_seq2(* b *)

    • 设置要比较的第二个序列。要比较的第一个序列不变。
  • find_longest_match((* alo ahi blo bhi *)

    • a[alo:ahi]b[blo:bhi]中找到最长的匹配块。

如果Ellipsis* isjunk None,则find_longest_match()返回(i, j, k),使得a[i:i+k]等于b[j:j+k],其中alo <= i <= i+k <= ahiblo <= j <= j+k <= bhi。对于所有满足这些条件的(i', j', k'),附加条件k >= k'i <= i',以及i == i'j <= j'也要满足。换句话说,在所有最大匹配块中,返回最早在 a 中开始的块,在所有最大匹配块中,最早返回 a 中的块,返回最早在 b *中开始的块。

>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=0, b=4, size=5)

如果提供了* isjunk *,则首先如上所述确定最长的匹配块,但附加的限制是该块中不会出现垃圾元素。然后,pass在两侧匹配(仅)垃圾元素来尽可能扩展该块。因此,结果块永远不会在垃圾上匹配,除非相同的垃圾恰好与某个有趣的匹配相邻。

这是与之前相同的示例,但考虑到空白是垃圾。这样可以防止' 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)

此方法返回named tuple Match(a, b, size)

  • get_matching_blocks ( )
    • 返回描述非重叠匹配子序列的三 Tuples 列表。每个三 Tuples 的形式为(i, j, n),表示a[i:i+n] == b[j:j+n]。三 Tuples 在* i j *中单调增加。

最后一个三 Tuples 是一个虚拟对象,其值为(len(a), len(b), 0)。它是n == 0的唯一三 Tuples。如果(i, j, n)(i', j', n')是列表中的相邻三 Tuples,而第二个不是列表中的最后一个三 Tuples,则i+n < i'j+n < j';换句话说,相邻的三 Tuples 总是描述不相邻的相等块。

>>> 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 ( )
    • 返回 5Tuples 的列表,描述如何将* a 转换为 b 。每个 Tuples 的形式为(tag, i1, i2, j1, j2)。第一个 Tuples 具有i1 == j1 == 0,其余的 Tuples 的 i1 等于前一个 Tuples 的 i2 ,同样, j1 等于前一个 j2 *。
  • tag *值是字符串,具有以下含义:
ValueMeaning
'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](子序列相等)。

For example:

>>> 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'
  • get_grouped_opcodes(* n = 3 *)
    • 返回最多+1 个上下文行的generator个组。

get_opcodes()返回的组开始,此方法会拆分较小的更改集群,并消除没有更改的中间范围。

这些组以与get_opcodes()相同的格式返回。

  • ratio ( )
    • 以[0,1]范围内的浮点数形式返回序列相似性的度量。

其中 T 是两个序列中元素的总数,M 是匹配数,这是 2.0 * M /T。请注意,如果序列相同,则为1.0;如果没有相同之处,则为0.0

如果尚未调用get_matching_blocks()get_opcodes(),则计算成本很高,在这种情况下,您可能需要先tryquick_ratio()real_quick_ratio()以获得上限。

Note

警告:ratio()调用的结果可能取决于参数的 Sequences。例如:

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

    • 相对较快地返回ratio()的上限。
  • real_quick_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 Examples

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

>>> 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()返回的最后一个 Tuples 总是(len(a), len(b), 0),这是唯一的情况,最后一个 Tuples 元素(匹配的元素数)是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]

See also

Differ Objects

请注意,Differ生成的增量不会声称是“最小”差异。相反,最小差异通常是违反直觉的,因为它们尽可能地同步,有时偶然相距 100 页。将同步点限制为连续匹配可以保留局部性的概念,但偶尔会产生较长的差异。

Differ类具有以下构造函数:

    • class * difflib. Differ(* linejunk = None charjunk = None *)
    • 可选关键字参数* linejunk charjunk *用于过滤器Function(或None):
  • linejunk *:一个函数,它接受单个字符串参数,如果字符串为垃圾,则返回 true。默认值为None,这意味着没有行被视为垃圾。

  • charjunk *:一个函数,它接受单个字符参数(长度为 1 的字符串),如果字符为垃圾,则返回 true。默认值为None,表示没有字符被视为垃圾字符。

这些垃圾过滤Function可加快匹配速度以发现差异,并且不会导致任何不同的行或字符被忽略。阅读find_longest_match()方法的* isjunk *参数的描述以获取解释。

Differ对象pass一种方法使用(生成增量):

  • compare(* a b *)
    • 比较两条线序列,并生成增量(一条线序列)。

每个序列必须包含以换行符结尾的单个单行字符串。此类序列可以从文件类对象的readlines()方法获得。生成的增量还包含换行符终止的字符串,这些字符串可以pass文件状对象的writelines()方法按原样打印。

Differ Example

本示例比较两个文本。首先,我们设置文本,以换行符结尾的单个单行字符串的序列(此类序列也可以从类似文件的对象的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的 Util。它也以Tools/scripts/diff.py的形式包含在 Python 源代码发行版中。

#!/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()