Python语言学习之是时候丢弃 Python 2.0,将 100 万行的代码迁移到 Python 3.0 了!
小标 2019-03-18 来源 : 阅读 1019 评论 0

摘要:本文主要向大家介绍了Python语言学习之是时候丢弃 Python 2.0,将 100 万行的代码迁移到 Python 3.0 了,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助。

本文主要向大家介绍了Python语言学习之是时候丢弃 Python 2.0,将 100 万行的代码迁移到 Python 3.0 了,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助。

Python语言学习之是时候丢弃 Python 2.0,将 100 万行的代码迁移到 Python 3.0 了!

Python 2 vs Python 3,究竟谁是性能之王?前段时间,Hackermoon 上一位叫 Anthony Shaw 的作者为我们做了一些测试,最终得出结论,虽然 Python 2 在加密和启动时间测试过程中,比 Python 3 的速度更胜一筹,但整体而言,Python 3 更快。


而这是否就意味着我们还是将项目代码迁移到 Python 3.0 的好?接下来,本文来自全球著名的桌面应用之一的 Dropbox 将分享他们要弃用 Python 2.0 的真实原因,以及如何将百万行的代码成功迁移至 Python 3。



Dropbox 是世界上流行的桌面应用之一,你可以安装在 Windows、macOS 和部分的 Linux 发行版上。但你可能不知道,这个应用大部分是用 Python 写的。实际上,Drew 给 Dropbox 写下的第一行代码就是用的 Windows 版 Python,用的是老牌的 pywin32 等库。


虽然我们靠着 Python 2 支撑了这么多年(我们用过的最新版本是 Python 2.7),但我们从 2015 年就开始向 Python 3 转换了。今天我们终于完成了转换,你现在再装 Dropbox 的话,那么它用的是 Dropbox 定制版本的 Python 3.5。本文将介绍这次史无前例的 Python 3 转换的计划、执行和发布过程。


为什么选择 Python 3?


Python 3 的接受度在 Python 社区一直是热门话题。现在虽然 Python 3 已经广为接受(//py3readiness.org/),一些非常流行的项目如 Django 甚至完全放弃了 Python 2 的支持,但这个话题的热度依然存在。对于我们来说,影响我们决定进行转换的几个关键因素有:


引人入胜的新功能


Python 3 的创新十分迅速。除了一长列(//whypy3.com/)正常的改进(如 str 和 bytes 的讨论),还有几个功能吸引了我们的眼球:


类型标注语法:我们的代码量非常大,所以类型标注对于开发的效率非常重要。在 Dropbox 我们很喜欢 MyPy(//mypy-lang.org/),因此原生的类型标注支持对我们很有吸引力。


并行函数语法:许多功能都极度依赖线程和消息传递,我们采用的是 Actor 模式,使用了 Future 模块。而 asyncio 项目及其 async/await 语法有时能避免回调函数,从而获得更干净的代码。


过老的工具链


随着 Python 2 日久年深,最初适合部署的工具链也大部分过时了。由于这些因素,继续使用 Python 2 会带来一系列的维护负担:


过老的编译器和运行时使得我无法们升级一些重要更新。 例如,我们在 Windows 和 Linux 上使用 Qt,而最新版本的 Qt 包含了 Chromium(通过 QtWebEngine 实现),因此需要更现代的编译器。


我们与操作系统的集成越来越深,而无法使用新版本的工具链,导致使用新版 API 的成本增大。 例如,理论上 Python 2 依然需要 Visual Studio 2008 (//stevedower.id.au/blog/building-for-python-3-5/)。但这个版本微软已经不再支持了,也与 Windows 10 SDK 不兼容。


冻结和脚本


当初,我们依靠“冻结”脚本为我们支持的每个平台创建原生应用程序。但是,我们并没有直接使用原生的工具链,如 macOS 的 Xcode,而是将创建各个平台上的二进制文件的任务交给其他程序去做,Windows 下是 py2exe,macOS 下是 py2app,Linux 下是 bbfreeze。这个完全面向 Python 的构建系统收到了 distutils 的启发,因为我们的应用最初只不过是个 Python 包,所以只需要一个类似于 setup.py 的脚本来构建。


随着时间的流逝,我们的代码量越来越大。现在,我们的开发已经不仅仅使用 Python 开发了。实际上,我们的代码现在由 Type/HTML、Rust 和Python 混合组成,某些平台上还用了 Objective-C 和 C++。为支持所有组件,setup.py 脚本(内部的名字为 build-all.py)越来越大,越来越难以管理。


导火索就是我们与各个操作系统集成的方式。首先,我们越来越多地引入高级的 OS 扩展,如 Smart Sync 的内核组件等,这些组件不能,通常也不会使用 Python 编写。其次,像微软和苹果等供应商对部署应用提出了新的需求,因此经常需要用到新的、更复杂的工具,这些工具经常是这些供应商独有的(比如代码签名等)。


例如在 macOS 上,10.10 版本引入了新的应用扩展以便与 Finder 进行集成,就是FinderSync(https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Finder.html)。它并不只是个 API,而是个完整的应用程序包(.appex),有自己的生存中崛起规则(即它由 OS 启动),而且对于进程间通信的要求更严格。换句话说,使用 Xcode 就很容易集成这些扩展,但 py2app 根本不支持它们。


因此,我们面临着两个问题:


由于我们使用 Python 2,因此无法使用新的工具链,所以集成新的 API 的代价更高(比如使用Windows 10的Windows Runtime)。


我们的冻结脚本使得部署原生代码的代价更高(例如在 macOS 上构建应用扩展)。


当我们计划转换成 Python 3 时,我们面临着两个选择:一是改进冻结脚本中的依赖,以支持 Python 3(从而支持现代编译器)和平台相关的功能(如应用程序扩展),二是不再使用以 Python 为中心的构建系统,完全放弃冻结脚本。我们选择了后者。


关于 pyinstaller 的一点:我们认真地思考过在项目早期使用 pyinstaller,但当时它不支持 Python 3,而且更重要的是,它和其他冻结脚本有类似的限制。不管怎样,这个项目本身很不错,我们只是觉得不适合我们而已。


嵌入 Python


为了解决构建和部署的问题,我们决定使用新的架构,在原生应用中嵌入 Python 运行时。我们不再将构建过程交给冻结脚本处理,而是使用各个平台自己的工具链(比如 Windows 下使用 Visutal Studio)来构建各种入口点。进一步,我们将 Python 代码抽象到一个库中,从而为多种语言“混合”的方式提供更直接的支持。


这样我们就可以直接使用各个平台的 IDE 和工具链了(例如可以直接添加原生的构建目标,如 macOS 上的 FinderSync),同时保留使用 Python 编写大部分应用程序逻辑的能力。


我们最后采用了下面的结构:


原生入口点:这些与各个平台的应用程序模型兼容。 其中包括应用程序扩展,如 Windows 下的 COM 组件和 macOS 下的应用程序扩展。


共享库可以使用多种语言编写(包括 Python)。


表面上,这个应用能够更接近平台的要求,而在各个库的背后,我们可以有更大的灵活性来选择自己喜欢的语言和工具。


这种架构能提高模块性,同时还带来一个关键的副作用:现在可以同时部署 Python 2 库和 Python 3 库了。联系到 Python 3 转换工作,我们的转换过程就需要两步:第一,给 Python 2 实现新的架构;第二,利用它将 Python 2 替换成 Python 3。


第一步:“解冻”


第一步就是停止使用冻结脚本。目前,bbfreeze 和 pywin32 都不支持 Python 3,所以我们别无选择。我们从 2016 年开始逐步进行这项改变。


首先,我们将配置 Python 运行时的工作抽象化,将 Python 的东西放到一个新的库中,名为 libdropbox_bootstrap。这个库会代替一些冻结脚本提供的功能。尽管我们不再需要这些脚本,但它们仍然提供了一些运行 Python 代码所需的最基本的东西:


打包代码以便在设备上执行


这样我们才能发布编译好的 Python 字节码,而不用发布 Python 源代码。由于以前的每个冻结脚本在各个平台上有各自的格式,我们利用这个机会引入了一种新的格式,用于在所有平台上打包代码使用:


所有 Python 模块的 Python 的字节码 .pyc 都放在单一的 zip 文档中(如 python-packages-35.zip)。


原生扩展. pyd / .so 由于是平台相关的原生动态链接库,他们必须安装在特定的位置,保证应用程序能毫无障碍地加载。 Windows 下,这些文件与入口点(即 Dropbox.exe)放在一起。


打包通过优秀的 modulegraph(作者是 py2app 和 PyObjC 的作者 Ronald Oussoren)实现。


隔离 Python 解释器


这样能阻止我们的应用程序在设备上运行其他的 Python 源代码。有意思的是,Python 3 使得这种嵌入变得容易得多了。例如,新的 Py_SetPath 函数(https://docs.python.org/3/c-api/init.html#c.Py_SetPath)能够让我们将代码隔离,不需要再像 Python 2 时代在冻结脚本中进行某种复杂的隔离操作了。为了在 Python 2 中支持这一功能,我们在定制版本的 Python 2 中向下移植了这一功能。


其次,我们使用了平台相关的入口点Dropbox.exe、Dropbox.app和dropboxd 来使用这个库。这些入口点都是用各自平台的“标准”工具编译的,即 Visual Studio、Xcode 和 make,没有使用 distutils。这样我们就可以去掉冻结脚本带来的大量修补工作了。例如,在 Windows 下,这一步大大简化,只需为 Dropbox.exe 配置 DEP/NX 即可,就能将应用程序装箱单和资源嵌入了。


关于 Windows 的一点说明:现在,继续使用 Visual Studio 2008 的代价已经非常高了。为了正确地转换,我们需要一个能同时支持 Python 2 和 Python 3 的版本,最终我们采用了 Visual Studio 2013。为支持它,我们进一步修改了定制版本的 Python 2,使之能正确在 Visual Studio 2013 下编译。这些修改的代价进一步证明,我们转换到 Python 3 的决定是正确的。


第二步:混合


成功地转换如此之大(包含大约 100 万行 Python 代码)、安装量如此之高(大约有几亿安装)的应用程序需要逐步进行。我们不能简单地在某次发布中“改变一个开关”来实现转换,特别是我们的发布过程要求每两个星期给所有用户发布一个新版本。因此,必须找到一种办法,将 Python 3 的部分转换发布给一小部分用户,以便检测并修改 Bug。


为达到这一点,我们决定实现用 Python 2 和 Python 3 同时编译 Dropbox。这要求做到以下两点:


能够同时发布 Python 2 和 Python 3 的“包”,包括字节码和扩展,两者必须能够并存。


在转换过程中强制使用混合的 Python 2 / 3 语法。


我们采用上一步引入的嵌入式设计来实现:将 Python 代码抽象到库和包中,就能很容易地引入另一个版本。这样入口点程序(即 Dropbox.exe)就可以在初始化的早期控制选择哪个 Python 版本了。


我们通过手动连接入口点程序到 libdropbox_bootstrap 来实现这一点。例如在 macOS 和 Linux 下,我们在 Python 版本确定之后使用 dlopen/dlsym 来加载。在 Windows 下,使用 LoadLibrary 和 GetProcAddress。


对 Python 解释器的选择必须在 Python 加载之前完成,因此为了使之更顺畅,我们实现了命令行参数 /py3 用于开发,和一个保存在硬盘上的永久设置,以便通过我们的功能切换系统Stormcrow(https://blogs.dropbox.com/tech/2017/03/introducing-stormcrow/)来控制。


有了这些,我们就能在启动 Dropbox 客户端时动态选择 Python 版本了。这样就可以在 CI 基础设施中设置额外的任务来针对 Python 3 运行单元测试和集成测试。我们还在提交队列中增加了自动检查,以防止提交会破坏 Python 3 支持的改动。


通过自动测试确保没问题之后,我们就开始将 Python 3 的改动推送给真正的用户。我们通过远程的功能开关来将新功能逐渐开放给用户。首先对 Dropbox 推送改动,这样我们就能找出并改正大部分主要的底层问题。然后将范围扩大到 Beta 用户,他们的 OS 版本问题更加芜杂。然后最终扩展到稳定版。7 个月之后,所有的 Dropbox 都已经在运行 Python 3 了。为了尽可能提高质量,我们要求所有与转换相关的 bug 必须进行深入调查并彻底修复,才能扩大推送的范围。


本文由职坐标整理并发布,希望对同学们学习Python有所帮助,更多内容请关注职坐标编程语言Python频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程