为什么我需要自己的diff工具?

我经常使用git跟踪我的编码项目、文章、业务工作等等。git的一个美妙之处在于,你可以通过简单地使用其内置的diff功能来轻松地比较你的工作的不同状态。要使用这个功能,你只需要满足两个约束:首先,你需要一个git存储库,其次,该文件需要由git存储库进行跟踪。

但是,如果您只想修改单个文件,并将其与旧版本进行比较,所有这些操作都不需要用到git存储库,那该怎么办呢?这就是本文的意义所在。本文的目标是创建一个diff工具,它允许你比较一个文件的两个版本:

  • 不需要git存储库和

  • 构建在Python标准库之上!

此外,我们的diff工具应该能够将计算的差异导出到一个HTML文件中。要阅读这篇文章,你只需要会Python。本文是专门为Python 3.8.2 (CPython)编写的。你可以在GitHub上找到源代码。就介绍到这里,让我们来看看它吧!

unified_diff() 函数

Python标准库包含一个名为difflib的模块。根据文档,这个模块提供了用于比较序列的类和函数。此外,还有各种可用的输出格式[1]。

在检查该模块时,unified_diff()函数从所有其他函数中脱颖而出。通过查看文档提供的示例和生成的输出,我发现它与我们正在寻找的差异计算函数非常相似。它需要多达8个参数,但只有两个是必需的:

  • a:字符串列表(必需)

  • b:字符串列表(必需)

  • fromfile:用于显示第一个文件的名称(默认值:'')

  • tofile:用于显示第二个文件的名称(默认值:'' )

  • fromfiledate:第一个文件的修改时间(默认值: '')

  • tofiledate:第二个文件的修改时间(默认值: '')

  • n:上下文行数*(默认值:3)

  • lineterm:添加在控制行(带有—、+++或@@的行)末尾的字符,以便io.IOBase.readlines()和io.IOBase.writelines()能被正确处理(默认值:'\n')。

*上下文行用于向用户在发生更改的地方提供上下文

实际上,unified_diff()函数会接受两个字符串列表并对它们进行比较。如果它们是相等的,delta就为空。如果存在任何差异,则返回各自的delta。举个简单的例子:你打算和你最好的朋友一起举办一个披萨派对,然后写下了你需要先买的配料。为简单起见,此购物清单是一个简单的文本文件(my_shopping_list.txt),内容如下所示:

你把它发送给你的朋友,他加了一种配料,因为你们家里都没有了:salami(意大利腊肠)。此外,他还识别出了你的打字错误并进行了纠正。为了能够正确地将这些更改传递给我们的diff工具,他复制了该清单并将其重命名为friends_shopping_list.txt。以下是最终的购物清单:

当然,这是一个相当简单的例子,文本不是很长,所以能很容易地被人为处理。但是,让我们增加点乐趣,我们继续看这个例子。为了计算这两个文件之间的差异,我们将它们读入内存,并将它们传递给unified_diff()函数:

首先,我们导入了difflib和sys。其次,我们读取这两个文件的内容,并将它们保存到单独的变量(file1和file2)中。因为我们需要字符串列表,所以我们使用了readlines()。随后,我们计算了两个列表的delta,并通过sys.stdout.writelines()将其写到stdout。

执行脚本后生成的结果如下:

从输出来看,单词cheese没有被修改,而是被用作上下文行,因为下面的行被修改了。tomates被移除了,tomatoes和salami被加了进去。如果您查看打印出的delta的头部,你可以看到,我们并没有得到有关--- 和 +++代表什么,或者它们代表什么文件的任何信息。让我们通过将文件名添加到unified_diff()函数来调整一下脚本:

现在,运行该脚本生成结果如下:

太棒了!我们实现了一个简单的脚本,它可以计算并打印两个文件内容之间的差异。让我们继续并将其转换为一个命令行工具。

构建一个命令行工具

为了将我们的脚本转换成一个有用的命令行工具,我们使用了Python的argparse模块。首先,我们将之前编写的代码放入一个名为create_diff()的函数中,该函数接受两个参数,old_file和new_file。它们都是Path对象[2]。我们使用传递的Path对象来读取它们的内容,并使用该Path对象的name属性来获取所提供的文件的名称。由于我们的小脚本现在是要更加通用,不再局限于购物清单,所以我们将我们的代码放入一个名为diff_tool.py的新文件中(这个名称更适合我们的脚本)。到目前为止,该脚本是这样的:

接着,我们定义一个新函数main(),它负责总的工作流:

首先,我们定义了一个新的参数解析器。我们告诉该解析器接受两个参数,old_file_version和new_file_version。两者都是必需的。调用parse_args()将解析命令行输入并将输入转换为正确的格式。随后,两个命令行参数都会被访问并且被转换为Path对象。然后,我们使用old_file和new_file作为参数调用create_diff()。

备注:如果您想要了解更多关于argparse模块的内容,我强烈推荐Python的argparse教程[3],它提供了更详细的Python命令行解析介绍。

现在,如果我们不带任何参数执行该脚本,它会告诉我们,哪些参数是必需的:

提供两个购物清单后仍然会生成预期的输出:

到目前为止,我们通过将你的简短脚本从头开始转换成一个简单的命令行工具来构建了一个简单的diff工具—很酷!现在,我们将添加更多的行来支持HTML输出。

以HMTL格式提供差异

difflib模块提供了一个HtmlDiff类,它可以被用来创建一个HTML表(或一个包含表的完整HTML文件),该表会通过将行间和行内变为高亮显示来显示文本的并排、逐行比较。在我们的示例中,我们使用了HtmlDiff.make_file()函数,它返回了一个表示完整HTML文件的字符串。后者逐行高亮显示了任何差异。

因此,我们将我们的脚本扩展如下:

create_diff()函数现在接受一个额外的第三个参数output_file,它也是一个Path对象。我们会将我们的HTML差异写入这个文件。我们检查是否传递了output_file。如果是,我们以HTML格式计算差异并将其保存到这个传递的文件中。

备注:我们使用w模式进行写操作。如果该文件已经存在,则会被提前清空。

如果output_file没有被传递,我们会计算标准差异并将其写到stdout。

我们通过注册一个附加的可选命令行参数--html使其以一个文件名作为输入来扩展了main()函数。如果提供了文件名,则将其转换为Path对象并传递给create_diff()。

执行以下命令之后,您的当前工作目录中就有了一个diff.html文件,你可以使用你最喜欢的浏览器来打开该文件去查看实际的差异。

总结

恭喜,你已经通过本文创建了它!在阅读本文时,你了解了如何使用Python的difflib模块计算一个简单的diff。此外,你还能够使用Python的argparse模块将您的短小diff脚本转换成一个命令行工具。随后,你添加了几行代码来支持以HTML作为输出格式。

接下来是什么?你可以查看difflib文档[1],了解计算diff的各种方法,搜索其他类型的diff,并进一步扩展你的diff-tool。另外,你可以查看本文的GitHub存储库并计算file.md和file_update.md之间的差异。你找到所有的变化了吗?

希望你喜欢阅读这篇文章。一定要与你的朋友和同事进行分享哦!如果你还没有,你可以考虑关注我的推特@DahlitzF。保持好奇心,持续编码!

参考资料

  1. difflib文档

  2. Path文档

  3. argparse文档

  4. 内置open()函数

英文原文:https://florian-dahlitz.de/blog/create-your-own-diff-tool-using-python 译者:测试

python diff函数_使用Python创建你自己的diff工具相关推荐

  1. python pos函数_使用python+sklearn实现特征提取

    sklearn.feature_extraction模块可用于以机器学习算法支持的格式从原始数据集(如文本和图像)中提取特征.**注意:**特征提取与特征选择有很大不同:前者是将任意数据(例如文本或图 ...

  2. python isalnum函数_探究Python中isalnum()方法的使用

    探究Python中isalnum()方法的使用 isalnum()方法检查判断字符串是否包含字母数字字符. 语法 以下是isalnum()方法的语法: str.isa1num() 参数 NA 返回值 ...

  3. python deepcopy函数_用Python解数独[6]:递归获得最终答案

    目录 用Python解数独[0] 用Python解数独[1]:求每个单元格的行值域 用Python解数独[2]:求列值域和九宫格值域 用Python解数独[3]:求总值域 用Python解数独[4]: ...

  4. python 微积分 函数_用Python学微积分(2)---复合函数

    函数的复合(Composition) 定义:设函数y=f(u)和u=g(x)u=g(x),则函数y=f[g(x)]称为由y=f(u)和u=g(x)复合而成的复合函数,其中函数y=f(u)常常称为外函数 ...

  5. python 排名函数_一个危险的Python函数,不推荐使用

    本文由编程派根据 Hacker News 上曾经排名第一的文章 编译而来,作者 Hynek Schlawack 是一名德国软件工程师. 他建议, 除非是编写只兼容 Python 3 的代码而且清楚地了 ...

  6. python 编译函数_在Python的Django框架中编写编译函数

    当遇到一个模板标签(template tag)时,模板解析器就会把标签包含的内容,以及模板解析器自己作为参数调用一个python函数. 这个函数负责返回一个和当前模板标签内容相对应的节点(Node)的 ...

  7. python unique函数_《Python编程从入门到实践》json数据可视化练习详解

    <Python编程从入门到实践>16.2中,计算收盘价均值的程序有些不易看懂,结合我自己的理解进行一些说明. 使用的数据集:join格式的数据, 数据集是由多个字典为元素组成的列表.每个字 ...

  8. python scapy 函数_【python|scapy】sprintf输出时raw_string转string

    最近在有python的scapy模块分析TCP报文,一直有一个关于转义字符的问题困惑着我,查找的很多资料后仍然百思不得其解,请大神指教. 请看代码: from scapy.all import * d ...

  9. python drop函数_用python帮财务小姐姐自动生成财务报表

    ↑↑↑关注后"星标"简说Python 人人都可以简单入门Python.爬虫.数据分析简说Python严选 来源:python数据分析之禅   作者:小dull鸟 One old w ...

最新文章

  1. 程序员该有的职业素养
  2. php pdf 文字水印图片,php pdf添加水印(中文水印,图片水印)
  3. 2018.3.29 网页中嵌套网页的两种方法
  4. Java基础day5
  5. 毕业季,我的Linux求职之路
  6. Android开发学习笔记:数据存取之File浅析
  7. 信号量内核对象 semaphore
  8. Ulipad快捷键大总结
  9. 电脑连接不上wifi,怎么办?
  10. 实用供暖通风空调设计手册 第三版_实用供热空调设计手册(第三版)“流体输配与水力平衡”技术专题讨论会...
  11. python之whl文件解释与安装
  12. msdos gpt
  13. C# 实现实时网速
  14. python入门(六)——python数据容器
  15. phonegap-第三方登陆-andriod插件
  16. (一)计算机基本组成
  17. Excel-VBA 快速上手(二、条件判断和循环)
  18. 栅格计算器函数之Con
  19. 自由转场低温吹风不伤发,诗恩无线吹风机防水级更安全
  20. 2022.09青少年软件编程(Python)等级考试试卷(二级)

热门文章

  1. Android实现自定义的 时间日期 控件
  2. 关于linux挂载windows共享文件夹的试验
  3. C# 对WinForm应用程序的App.config的使用及加密
  4. oracle 11gr2 bbed 安装,oracle11gR2 安装bbed工具
  5. Java处理split分割【for循环】
  6. EditPlus行首行尾批量添加字符 以及其它常用正则
  7. mysql分库分表总结
  8. linux下lua开发环境安装
  9. RabbitMQ消息重复消费问题
  10. Vue中splice的使用:删除 替换 添加