- 原文地址:Moving a large and old codebase to Python3
- 原文做者:Anders Hovmöller
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:Starrier
- 校对者:LynnShaw,steinliber
一年半前,咱们就决定使用 Python 3 了。咱们已经讨论了很长时间,如今是时候使用了!如今这个过程已经结束了,咱们已经把生产环境的最后部署都迁移到了 Python 3html
关于修改 Python 3 的一些基本统计数据,是基于对 git 提交历史的粗略过滤产生的:前端
我发现有 109 个 jira 问题与这个项目相关。python
咱们的理念一直是 py2 →py2/py3 → py3 由于咱们实在没法在实际生产中实现巨变,这种直觉也以使人惊讶的方式被证实是正确的。这意味着 2 到 3 是不可能的,我认为这很常见。咱们尝试过使用 2 to 3 来检测 Python 3 的兼容性问题,但很快这也被发现没法成立。基本上,这样的更改意味着在 Python 2 中的代码将被破坏。这样的改变不可行。android
结论是使用 six, 这是一个库,能够方便的构建一个在 Python 2 和 3 中都有效的代码库。ios
首当其冲的就是更新以前的依赖关系。这项工做须要马上启动,由于以后会有更多的内容要更新。git
Python-modernize 是咱们选择进行迁移的工具。它是一个能够自动将 Py 2 代码库转换为可兼容 six 代码库的工具。咱们首先引入一个测试,做为 CI 的一部分,来检查基于 modernize 的新代码是否已经准备好兼容 py3 了。这样作最大的效果的是让那些仍使用 Py 2 语法的人意识到新的处理方法,但这显然对将现有的 240 k 行代码转化到 six 做用不大。咱们都有使用旧语法的坏习惯,这能够说是教学上的成功了,即便它对代码行的计数没有什么不一样,它也被咱们用于实验分支:github
我新建了一个名为“Python 3 ”的分支,并作了如下操做:数据库
这里的想法是“run ahead”,即看看若是咱们没有使用过期的依赖项,咱们会遇到什么问题。这个分支容许我在超级中断状态下能够很是快速地启动应用程序,至少能够运行一些单元测试。 这个分支有很大的不一样,但我仍是找到了把它应用在适当场景的方法。我使用优秀的 GitUp 来拆分、组合和提交。当一个提交看起来不错的时候,我会把它挑选到一个新的分支,而后发给代码审查。后端
没有人能够在这个分支上工做,由于它被不断地 rebase ,强制推送,滥用,可是它确实让项目向前推动了,而不用等待全部的依赖项被更新。我强烈推荐使用这种方法!函数
咱们添加了预提交钩子,因此若是您编辑了一个文件,就会收到建议将 Python 3 所有进行 modernize 更新的提示。
quote_plus
的手动静态分析: 在处理 quote_plus 和 six 上有一些细微差异。最后,咱们建立了本身的包装器,默认代码强制执行使用这个包装器,而不是使用标准库中的包装器,也不使用 six 中包装器。咱们还静态检查了您从未给 quote_plus 发送过的字节。
咱们修复了每一个 diango 应用程序中全部的 python 3 问题,并在 CI 环境中使用一个白名单强制执行了这一点,因此您没法破坏一个曾经修复过的应用程序。
对于咱们来讲,解决依赖是最困难的部分。咱们有不少依赖,因此花了不少时间,其中有两个依赖关系比较棘手:
咱们的代码测试覆盖率大约有 65% 包括:单元、集成, 以及 UI 合并。 咱们确实编写了更多的测试,但整体数量并无发生太大的变化。考虑将覆盖率从 65% 提升到 66% ,意味着编写将近2000 行代码的测试,这一点也不奇怪。
咱们必须跳过须要 Cassandra 的测试,同时修复这个依赖项。 我发明了一个有趣的小 hack 来使它发挥做用, 并写了这方面的文章.
关于代码更改的说明,在如何将 py2 迁移到 six 的文档中并未说起 (也许是咱们错过了):
咱们在代码中大量使用 StringIO 。第一反应就是使用 six。但对于 StringIO 来讲,这在几乎全部状况下 (但不是所有!)都被证实是错。基本上,咱们必须很是仔细地考虑每个咱们使用 StringIO 的地方,并试图弄清楚咱们是否应该用 io.StringIO, io.BytesIO 或者 six.StringIO 来替代它。这里犯错的表现一般为看起来像兼容 py3 的代码准备好了,在 py2 中能够正常运行,却实际上在 py3 中是失效的。
这是一件好坏参半的事情。您能够经过将它添加到许多文件中来发现 bug,可是有时会在 py2 中引入 bug。 当日志忽然在奇怪的地方,好比在字符串前写"u"时,它也会变得使人困扰。总的来讲,这显然不是我所指望的效果。
这在很大程度上是您所指望的。我感到惊讶的是,在 py2 和 py3 中须要 str 。若是未来您使用 unicode_literals 导入,那么一些字符串须要从 'foo'
修改成 str('foo')
。
six.moves 的实现是一个很是奇怪的黑客行为,所以它不像它伪装的普通 Python 模块那样运行。 我也不一样意他们在 six.moves 中不包含 mock
的选择。咱们必须使用他们的 API 来本身添加它,但这让咱们很难开始工做,并且它要求咱们将 from mock import patch
改成 from six.moves import mock
这也意味着 patch
如今变成了 mock.patch
。
若是你使用 csv 模块,你须要了解 csv342。在我看来,这应该是 six 的一部分。不然就意味着你没有意识到有问题。不过咱们在许多地方都没有使用 csv342,因此您这里要作的工做可能会有所不一样。
咱们首先进行测试:
接下来就是产品自己了。咱们创建一台拥有能一次性切换到 py3 的能力的批处理机器,而且相当重要地是将其切换回来。当在 py3 上发生中断时,这一点就显得很重要了。这对咱们来讲是很好的,由于咱们能够从新排队那些中断的任务,可是咱们不能中断太多或者任何其实是很关键的任务。咱们使用 Sentry 来收集奔溃日志,因此很容易查看迁移到 py3 时遇到的全部问题,并且当咱们修复了全部的问题时,咱们须要再次迁移到 py3,直到咱们获得一些问题,如此反复。
咱们有以下环境:
咱们按照如下顺序将 Python 3 发布到这些环境中:
负载机器暴露了与 Python 3 不兼容的客户数据配置,所以咱们必须在 Python 2 中实现对这些状况的警告,并确保再次打开 Python 3 以前已经修复了它们。这花了几天时间,由于咱们天天都会收到客户数据,因此每次都会有一个警告,这又让咱们不得再也不等一天。
'ß'.upper()
在 py2 中是 'ß'
可是在 py3 中是 'SS'
。当产品的最后一部分迁移到 py3 时,最终致使了产品的崩溃!None
的时候。总的来讲,这是一个胜利,由于咱们发现了至关多的 bug 。 None
在 py2 的列表中排在第一位,这可能会让人感到惊讶(您可能会指望它被排序到接近于零的地方!), 如今咱们只须要来处理它们。'{}'.format(b'asd')
在 Python 2 中是 'asd'
, 可是在 Python 3 中是 "b'asd'"
。在 Python 3 中,这里几乎任何其余行为都会更好: 输出为十六进制 ( 结果明显更不同 ) ,旧的行为 (以前的代码运行),或者抛出异常 (最好的行为!)。int('1_0')
在 py 3 中结果是 10 , 可是在 py2 中无效。这甚至在切换到 py3 以前就困扰了咱们。由于这种错配致使了另外一个在咱们以前使用 py3 的团队给咱们发送了咱们认为无效而他们认为有效的有效值。我我的认为这个决定是错误的:很是严格的解析是更好的默认方式,我担忧这将在将来几年会继续以微妙的方式困扰咱们。最后,咱们以为在这件事上咱们真的别无选择: Python 2 的维护将在某个时刻中止,咱们的依赖项仅限于 py3,最明显的就是 Django。可是,不管如何,咱们仍是想要进行这种转换,由于咱们常常会被 bytes/Unicode 问题困扰,而且Python 3 仅仅是修复了 Python 2 中的许多小麻烦。此次迁移过程,咱们已经在生产过程当中发现了一些实际的漏洞/错误配置。咱们也期待在任何地方均可以使用 f-string 和有序字典。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。