写点什么

从 Python2 到 Python3:超百万行代码迁移实践

  • 2019-02-16
  • 本文字数:3240 字

    阅读完需:约 11 分钟

从Python2到Python3:超百万行代码迁移实践

全球有数百万用户使用 Dropbox 桌面客户端来保存其重要文件并在不同设备间同步文件。在从 Python 2 迁移到 Python 3 的过程中,我们要处理超过 100 万行 Python 代码逻辑,因此工作量巨大。在此过程中,我们明白必须不辜负用户对 Dropbox 的信任,并保证他们的信息安全。


在过去的几个月里,我们探讨了为什么要做以及如何做 Python 3 迁移,以及我们如何确保新的 Dropbox 应用是可靠的。在本文中,我们将介绍桌面客户端的 Python 3 简史,然后深入解析如何在允许持续开发的同时实现了逐步迁移。


先锋队

Dropbox 的年度黑客周(Hack Weeks)激发了许多伟大创意。将 Dropbox 桌面客户端迁移到 Python 3 也正是在此期间产生的。公司内部用来描述黑客周的词汇是,“回归本源” - 在黑客周的五天里,Dropbox 的所有人都把他们的日常工作放在一边,组建成小型的、行动迅速的团队,解决令人兴奋或有趣的问题。

黑客周 2015

一切始于此处 - 一只黑客周团队决定大胆尝试,看看用 Python 3 编写 Dropbox 桌面客户端是否可行。本着黑客周的精神,他们推出了一个桌面客户端版本,这个版本可以在运行 Python 3 时进行登录和同步文件的操作。


问题解决了吗?并非如此。很不幸的是,显然有许多功能都被 Python 升级给完全破坏掉了。一些与 Python 2 和 Python 3 兼容的更改得到合并,但是大部分努力最终都付之东流了。

黑客周 2016

在黑客周期间,一支新团队成立了,其目标是为 Python 3 版本的客户端推出更多的稳定功能。借助 Mypy(Mypy 是我们在过渡时期采用的静态类型检查工具),他们在完成 Python 3 迁移方面取得了突破进展:


  • 将我们的 Python 自定义分支移植到 3.5 版本

  • 将一些 Python 依赖项升级到 Python 3 兼容版本,并将一些其他版本合并(例如 babel)

  • 修改了一些 Dropbox 客户端代码,使其与 Python 3 兼容

  • 在我们的持续集成(CI)中设置自动化作业,以使 Python 3 解释器运行现有单元测试,并在 Python 3 模式下运行 Mypy 类型检查工具


更加重要的是,自动化测试意味着,我们可以肯定:当项目重启时,现有的为数不多的 Python 3 兼容性不会消退。

Python 3 的魅力

到 2017 年初,升级 Python 版本已经成为数个工具链升级的关键路径,这已经是显而易见的。公司为将要持续数月的 Python 3 迁移项目正式配备了团队。

先决条件

在迁移任何应用程序逻辑之前,我们必须确保,可以正常加载 Python 3 解释器并从打开程序伊始就可以运行。过去我们使用 freezer 脚本,但当时这些都不支持 Python 3,所以在 2016 年末,我们构建了一个自定义的、更原生的解决方案,内部称之为 Anti-freeze(更多内容参见最初的关于 Python 3 迁移的博客文章 )。


有了这些基础,我们就可以开始客户端本身的迁移了。

1.逐步启用单元测试和类型检查

首先要做的是,在 Python 3 环境下启用所有单元测试和 Mypy 类型检查,以验证与 Python 3 的兼容性。


起初,在 Python 3 环境下所有单元测试全部禁用,这一步可以使用模块级的 pytest.skip 函数调用来实现。然后我们逐个检查测试文件,在 Python 3 下运行,修复应用程序逻辑本身的问题和测试中的任何问题,然后取消前面的禁用命令。


同样,我们有一个明确的文件黑名单,这些文件没有通过 Python 3 Mypy 下的单文件测试。我们在代码库中启用了 Python 3 Mypy,这样就可以利用公司范围内对 Mypy 的推动,从而可以添加更多 Mypy 类型(在此项目结束时,覆盖率已经从最初的 35% 提升到了 63%!)。我们还强制执行与 Python 2 和 Python 3 的同步兼容,防止语法混用导致消退。


特别是 Mypy 能够捕获并警告某些类型的问题,而这些问题会在 Python 3 上悄无声息地产生错误结果,例如我们最常见的问题是两个 Python 版本中 str,bytes 和 unicode 之间的行为差​​异。


字符串(Strings)同时有 str 、 bytes 和 unicode 三种表示,我的天啊太麻烦了。


简要总结:在 Python 2 中,str 是字节(bytes)的别名,unicode 是 Unicode 字符串的类型;在 Python 3 中,str 是 Unicode 字符串的类型,bytes 是字节字符串(byte-strings),没有 unicode。


除了命名类型不同之外,Python 在不同版本中处理这些类型的方式也存在显着的语义差异(所以我们针对这个话题单独进行举例)。为简洁起见,我们不做讨论。但是我强烈建议,任何从 Python 2 迁移到 Python 3 项目的人员都彻底掌握这些差异。


我们遇到的主要问题还涉及不同的内存位置,这些位置是各种数据在内存中的序列化的展示。因为接口通常接受类字符串的对象,所以我们很乐意在字节字符串(byte-string)上调用 str ,这会在 Python 3 中产生 “b’string contents”。Mypy 类型更加强大(要明确类型何时是字节(bytes)、何时是文本(Text))和单元测试套件的失败共同促使我们发现这一问题。


关于 from future import unicode_literals 的特别说明


从表面上看,这似乎很方便,因为它在 Python 2 中实现了 Python 3 字符串的行为。然而,我们发现,这仅仅在代码库的部分内容中使用起来就已经很让人困惑,并且不可能在一夜之间添加到每个文件中。


我们不可能直接在代码库中添加这个 import 命令,因为它可以改变代码运行时的行为,特别是许多 Python 标准库函数需要在 Python 2 和 Python 3 上同时传递 str 变量。


文件开头的 import 命令会导致字符串文字类型发生变化、跨文件追踪逻辑本就会令人迷惑,所以只在一些文件中包含这个 import 命令就已经容易引起曲解了。

2.跨越 Python 2 和 Python 3

在完成单元测试和 Mypy 类型检查之后,我们就开始端到端的应用程序测试了。


我们首先在团队内部进行,解决与基本功能相关的任何明显的问题,然后组织 “bug bashes” 。后者与开发 Dropbox 客户端不同部分的团队一起进行,这样可以更详细地测试他们的功能并发现更加细微的特性回调。


然后,就是内部用户吃自己的狗粮了-亲自采用 Python 3 版本的客户端。在发生重大问题的情况下,为了能够安全又快速地使用户迁移回 Python 2 ,我们构建了 Hydra,在桌面客户端启动时,Hydra 允许我们选择运行 Python 2 或 Python 3 的编译器。在此期间,我们必须确保所有应用程序逻辑都使用混合的 Python 2/3 语法编写(也就是’跨越’ Python 2 和 Python 3),这样我们可以向大多数用户提供 Python 2 版本的同时在内部测试 Python 3 版本。

3.顺其自然

为确保满足我们的高质量标准,我们将桌面客户端在这种混合状态下维持了超出计划的时长 - 先是我们的内部构建,最终是对外的 Beta 版本。


在此期间,我们依靠来自改进的聚合崩溃报告管道的报告提醒我们已发的问题。这最终引申出了一些有趣的话题,例如 Python 本身的这个问题。


在此期间大约 7 个月后,我们确信,Python 3 版本客户端符合我们的质量标准,将此版本扩展到稳定版本 (Stable channel) ,并从应用程序二进制文件中移除了 Python 2 。这标志着我们的 Python 版本迁移之旅的结束!

学习

  • 单元测试和类型检查极其重要。通过单元测试和静态 Mypy 类型检查,我们能够在早期发现大多数的兼容性问题,并且这也允许我们创建一个清晰同时可执行的问题修复列表。

  • Python 中的字符串编码很难。 Python 3 在这方面明显更加高效。如果你的 Python 逻辑需要处理 Unicode 字符串,这本身就是从 Python 2 切换到 Python 3 的极佳理由。然而,Python 3 中用于修复字符串行为的重大变化意味着,在迁移过程中发现的大多数问题都与版本之间字符串处理方式的差异有关。

  • 逐步迁移到 Python 3 以获取丰厚利润。因为我们在整个项目中保留了 Python 2 兼容性,所以我们可以继续做 Python 2 版本的功能开发并发布应用程序,同时逐渐增加 Python 3 的兼容性,直到时机足够成熟可以切换。

致谢

特别感谢 Max Belanger 在本文撰写过程中提供的编辑建议,感谢 John Lai 介绍了最早在 Dropbox 进行 Python 3 尝试的历史背景,感谢为此项目做出贡献的所有人(完整列表在这篇原始的博客文章里!) 。


原文链接:


https://blogs.dropbox.com/tech/2019/02/incrementally-migrating-over-one-million-lines-of-code-from-python-2-to-python-3/



2019-02-16 08:056180

评论

发布
暂无评论
发现更多内容

AI关于AI民主化的高见

FN0

AIGC

如何评估大型语言模型(LLM)?

Baihai IDP

人工智能 深度学习 大模型 白海科技 大模型评估

一文读懂ChatGPT的工作原理:大语言模型是个啥?它到底咋工作的?

禅道项目管理

#人工智能 ChatGPT AI 2022

使用华为云AstroZero,不用一行代码,制作端午节加班申请模板

云计算 零代码 华为云

Airtest图像识别测试工具原理解读&最佳实践 | 京东云技术团队

京东科技开发者

图像识别 移动开发 UI自动化测试 企业号 6 月 PK 榜 Airtest

一种实现Spring动态数据源切换的方法 | 京东云技术团队

京东科技开发者

spring aop 企业号 6 月 PK 榜 数据源切换

基础设施SIG月度动态:ABS新增ISO、VHD镜像构建,自动热补丁制作流程正式上线

OpenAnolis小助手

镜像 基础设施 龙蜥社区 sig abs

海外交友源码平台搭建:基础功能的实现(一)

山东布谷科技

软件开发、 源码搭建 海外市场 语音交友源码

AI+电力、大模型主题人工智能师资培训班重磅招募中

飞桨PaddlePaddle

人工智能 百度 paddle

海南正规等级保护测评单位有哪些?叫什么名字?

行云管家

等保 等级保护 海南 等保测评单位

IT自动化运维工具用哪款?需要考虑哪些因素?

行云管家

IT运维 自动化运维 IT自动化运维

模型当道 开源聚力|2023开放原子全球开源峰会开源大模型分论坛圆满收官

开放原子开源基金会

开源 大模型 开放原子全球开源峰会 开放原子

在人工智能冲击下,IT部门的生存价值在哪里?

FN0

AIGC

Vue3中常用的Composition(组合)API-watch(监视)函数

不觉心动

6 月 优质更文活动

手牵手带你实现mini-vue | 京东云技术团队

京东科技开发者

Vue 数据绑定 vue2 企业号 6 月 PK 榜 双向数据绑定

蚂蚁集团自动化混沌工程 ChaosMeta 正式开源

ChaosMeta

高可用 混沌工程 故障演练 kubernetes 运维 混沌测试

vivo 游戏黑产反作弊实践

vivo互联网技术

游戏黑产 游戏礼券

高性能网络 SIG 月度动态:联合 IBM 就 SMC v2.1 协议升级达成一致,ANCK 率先完成支持

OpenAnolis小助手

开源 ibm 高性能网络 anck 龙蜥sig

300行代码模拟cdn访问过程

蓝胖子的编程梦

CDN DNS CDN加速 CDN技术 #DNS

细说敏捷测试-敏捷实战中的探索 | 京东云技术团队

京东科技开发者

敏捷开发 测试 敏捷测试 企业号 6 月 PK 榜

详解4种模型压缩技术、模型蒸馏算法

华为云开发者联盟

人工智能 华为云 华为云开发者联盟 企业号 6 月 PK 榜

Java 内存与缓存管理:应对大数据场景的优雅高效策略

xfgg

Java 6 月 优质更文活动

可观测性最佳实践 | 警惕!未知的风险正在摧毁你的系统

观测云

可观测性 运维监控 观测云 云原生可观测 可观测性用观测云

强化学习从基础到进阶-案例与实践[1]:强化学习概述、序列决策、动作空间定义、策略价值函数、探索与利用、Gym强化学习实验

汀丶人工智能

人工智能 深度学习 强化学习 深度强化学习 6 月 优质更文活动

随机2D形状周围层流预测!基于飞桨实现图形神经网络

飞桨PaddlePaddle

人工智能 百度 飞桨

Java 中优雅的 RESTful API 设计:实现高效且易维护的接口

xfgg

Java RESTful API 6 月 优质更文活动

软件测试/测试开发丨Pytest结合数据驱动-CSV

测试人

程序员 软件测试 自动化测试 csv pytest

浅谈API安全

权说安全

API 安全

漫谈 SAP 产品里页面上的 Checkbox 设计与实现

汪子熙

SAP 前端设计 思爱普 6 月 优质更文活动

TBB 开源库及并发 Hashmap 的使用

KaiwuDB

KaiwuDB TBB开源库 Hashmap使用

从Python2到Python3:超百万行代码迁移实践_语言 & 开发_Cary Yang_InfoQ精选文章