我们如何推出有史以来最大的Python 3迁移之一
Dropbox是世界上最受欢迎的桌面应用程序之一:您现在可以将其安装在Windows,macOS和某些Linux版本上。您可能不知道的是,许多应用程序是使用Python编写的。实际上,Drew的Dropbox的第一行代码是使用Windows等古老的库在Python for Windows中编写的pywin32。
尽管我们已经依靠Python 2多年(最近,我们使用Python 2.7),但我们在2015年开始转向Python3。这种过渡现已完成:如果您今天使用的是Dropbox,则该应用程序将由Dropbox自定义的Python 3.5变体。这篇文章是探索我们如何计划,执行和推出有史以来最大的Python 3迁移之一的系列文章的第一篇。
为什么选择Python 3?
长期以来,Python 3的采用一直是Python社区争论的话题。尽管现在已经得到了广泛的支持,但在某些非常受欢迎的项目(例如Django完全放弃了对Python 2的支持)中,这仍然有些正确。对于我们来说,一些关键因素影响了我们做出跳跃的决定:
令人兴奋的新功能
Python 3有了快速的创新。除了(非常)一长串的一般改进(例如strvsbytes合理化)外,一些特殊功能引起了我们的注意:
类型注释语法:我们的代码库很大,因此使用类型注释的能力对于开发人员的生产力至关重要。我们在Dropbox上是MyPy的忠实拥护者,因此自然支持类型注释的能力自然吸引了我们。
协程函数语法:我们非常依赖线程和消息传递(通过Actor模式的变体并使用Futures)来构建许多功能。该asyncio项目及其async/await语法有时可以消除对回调的需要,从而使代码更简洁。
老化的工具链
随着Python 2的老化,最初兼容部署它的工具链集已经过时了。由于这些因素,继续使用Python 2带来了越来越大的维护负担:
使用较旧的编译器/运行时限制了我们升级某些重要依赖项的能力。
例如,我们在Windows和Linux上使用Qt:由于包含Chromium(通过QtWebEngine),因此Qt的最新版本需要更现代的编译器。
随着我们继续与操作系统进行深入集成,我们无法依赖这些工具链的最新版本增加了采用较新API的成本。
例如,Python 2在技术上仍然需要Visual Studio2008。Microsoft不再支持该版本,并且与Windows 10 SDK不兼容。
冰箱和脚本
最初,我们依靠“冷冻机”脚本为每个受支持的平台创建本机应用程序。但是,我们不是直接使用本机工具链(例如,用于macOS的Xcode),而是将创建平台兼容的二进制文件委派给py2exe了Windows,py2appmacOS和bbfreezeLinux。这个关注Python的构建系统的灵感来自于distutils:我们的应用程序最初只是一个Python包,因此我们有一个类似setup.py脚本来构建它。
Over time
引爆点来自变化如何,我们每个操作系统整合:首先,我们开始引进日益发达的操作系统扩展般的智能同步的核心部件,不能和经常不应该用Python写的。其次,像Microsoft和Apple这样的供应商开始对部署应用程序提出了新的要求,这些要求要求使用新的,更复杂的,通常是专有的工具(例如,代码签名)。
例如,在macOS上,版本10.10引入了新的应用扩展程序,可与Finder集成:。FinderSync扩展不仅仅是一个API,它是一个成熟的应用程序包(),具有自定义生命周期规则(即,由OS启动),并且对进程间通信的要求更加严格。换句话说,Xcode使利用这些扩展变得容易,而并不完全支持它们。[FinderSync].appexpy2app
因此,我们面临两个问题:
我们对Python 2的使用使我们无法使用新的工具链,从而使使用新API的成本更高(例如,在Windows 10上使用Windows运行时)。
我们对冷冻机脚本的使用使部署本机代码的成本更高(例如,在macOS上构建应用程序扩展)。
虽然我们知道我们想迁移到Python 3,但这给了我们一个选择:投资于冰箱的依赖项以增加对Python 3(以及现代编译器)和特定于平台的功能(如应用程序扩展)的支持,或者迁移摆脱了以Python为中心的构建系统,完全消除了“冰柜”。我们选择了后者。
关于的注释pyinstaller:我们在项目的早期阶段就认真考虑过使用它,但当时它不支持Python 3,更重要的是,它与其他冷冻器一样受到类似限制。无论如何,这是一个令人印象深刻的项目,我们只是觉得不符合我们的需求。
嵌入Python
为了解决此构建和部署问题,我们决定采用新的体系结构,将Python运行时嵌入到本机应用程序中。与其将这个过程委派给冰柜,不如使用针对每个平台的工具(例如Windows上的Visual Studio)自行构建各种入口点。此外,我们将在库后面抽象Python代码,旨在更直接地支持各种语言的“混合和匹配”。
这将使我们能够直接利用每个平台的IDE /工具链(例如,在macOS上添加诸如FinderSync之类的本机目标),同时保留使用Python方便地编写许多应用程序逻辑的能力。
我们采用以下粗略结构:
本机入口点:这些与每个平台的应用程序模型兼容。
这包括应用程序扩展,例如Windows上的COM组件或macOS上的应用程序扩展。
用多种语言(包括Python)编写的共享库。
从表面上看,应用程序将更类似于平台的期望,而在各种库的支持下,团队将拥有更大的灵活性来使用他们选择的编程语言或工具。
这种架构的增加的模块化也将带来关键的副作用:现在可以同时部署Python 2库和Python 3库。将其与Python 3迁移相关联,因此该过程将需要两个步骤:第一步,围绕Python 2实现新架构,其次,将其用于“交换” Python 2以便使用Python 3。
步骤1:“防冻”
我们的第一步是停止使用冷冻机脚本。双方bbfreeze并pywin32缺少的Python 3支持在这个阶段,留给我们别无选择。从2016年开始,我们开始逐步进行此更改。
首先,我们将配置Python运行时和启动Python线程的工作抽象到名为的新库中libdropbox_bootstrap。该库将复制一些冷冻机脚本提供的内容。尽管我们不再需要完全依赖这些脚本,但是仍然有必要提供运行Python代码的最低基础:
打包我们的代码以在设备上执行
这可以确保我们交付已编译的Python“字节码”,而不是原始Python源代码。在每个冷冻程序脚本以前都有自己的磁盘格式的地方,我们利用这次机会介绍了一种在所有平台上捆绑代码的单一格式:
对于Python字节码.pyc,单个ZIP存档(例如python-packages-35.zip)包含所有必需的Python模块。
对于本机扩展.pyd/ .so,因为它们是平台本机DLL,所以它们安装在保证应用程序可以不受干扰地加载的位置。
例如,在Windows上,它们位于入口点(即Dropbox.exe)旁边。
包装是使用优秀的modulegraph(由Ronald Oussorenpy2app并享有PyObjC声誉)实施的。
隔离我们的Python解释器
这将阻止我们的应用程序运行其他设备上的Python源。有趣的是,Python 3使这种类型的嵌入变得更加简单。 例如,新功能允许我们隔离代码,而不必执行冷冻脚本必须在Python 2上完成的一些更复杂的隔离工作。为了在Python 2中支持此功能,我们将此功能反向移植到了我们的代码中自定义叉子。[Py_SetPath]
其次,我们推出了特定于平台的切入点Dropbox.exe,Dropbox.app和dropboxd利用这个库。这些入口点是使用每个平台的“标准”工具(Visual Studio,Xcode)构建的,并且make使用而不是distutils,从而使我们能够消除对冷冻机脚本施加的许多自定义拼凑而成的东西。例如,在Windows上,这大大简化了DEP / NX的配置Dropbox.exe,嵌入应用程序清单以及包括资源。
关于Windows的说明:此时,继续使用Visual Studio 2008的成本变得很高。为了正确过渡,我们需要一个能够同时支持Python 2和3的版本,因此我们选择了Visual Studio2013。为了支持它,我们广泛更改了Python 2的自定义派生版,以使其可以使用该版本正确编译。这些变更的代价进一步强化了我们的信念,即迁移到Python 3是正确的决定。
步骤2:Hydra
成功地进行这种规模的转换(我们的应用程序包含超过100万个Python LOC),而在我们这样的规模(数亿次安装)上,则需要一个渐进的过程:我们不能简单地在一个发行版中“切换” —特别是由于我们的发布过程,该过程每两周向所有用户部署一次新版本。必须有一种方法可以使少量/不断增长的用户接触Python 3,以便及早发现并修复错误。为了实现这一目标,我们决定让它可以利用建立的Dropbox既Python 2和3,这需要:
能够同时交付带有字节码和扩展名的Python 2和Python 3“程序包”。
在过渡期间执行混合Python 2/3语法。
我们使用了上一步引入的嵌入式设计来发挥我们的优势:通过将Python抽象到库和包中,我们可以轻松地为另一个版本引入另一个变体。然后,可以Dropbox.app在早期初始化期间在入口点本身(例如)中控制要使用的Python版本。
这是通过使入口点手动链接来实现的libdropbox_bootstrap。例如,在macOS和Linux上,一旦选择了Python版本,我们就会使用dlopen/ dlsym。在Windows上,我们使用LoadLibrary和GetProcAddress。
需要在加载Python之前选择运行时Python解释器,因此我们可以使用/py3出于开发目的的命令行参数和持久的磁盘设置来影响它,从而可以由Stormcrow对其进行控制,我们的功能门控系统。
有了这个,我们就可以在启动Dropbox客户端时动态选择Python版本。这使我们能够在CI基础结构中设置其他作业,以运行针对Python 3的单元测试和集成测试。我们还将自动检查集成到提交队列中,以防止推送会退还Python 3支持的更改。
通过自动化测试获得足够的信心后,我们便开始向实际用户推出Python 3。这是通过通过远程功能选通增量选择客户端来实现的。我们首先对Dropboxers进行了更改,这使我们能够识别和纠正大多数潜在问题。后来,我们将其扩展到我们的Beta人口的一小部分(涉及OS版本时,它们的种类更加多样化),最终扩展到了我们的稳定频道:在7个月内,所有Dropbox安装都运行了Python 3。我们采用了一项政策,要求在扩展暴露的用户数量之前,应彻底调查并更正所有与迁移相关的错误。
在稳定频道上逐步推出从52版开始,此过程已完成:Python 2已从Dropbox的桌面客户端中完全删除。
在Beta频道上逐步推出
在稳定频道上逐步推出
从版本52开始,此过程已完成:Python 2已从Dropbox的桌面客户端中完全删除。

关注 CDA人工智能学院 ,回复“录播”获取更多人工智能精选直播视频!