写点什么

从 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:056072

评论

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

年末十家手机银行数字化升级大盘点:谁家开发更全面?谁家建设更到位?

CECBC

疫情 银行 手机银行

「架构师训练营」第 4 周作业

小黄鱼

极客大学架构师训练营

【概念篇】你真正了解越来越火的“数据驱动” 吗?

Java架构师迁哥

双“11”搞促销?本文教你用贪心算法来盘他!

Java架构师迁哥

阿里突遭断网断电!双11最惊险一幕刚刚曝光

Java架构师迁哥

面试官问我redis数据类型,我回答了8种

数据库 学习 面试

SpringBoot启动原理

编程门槛 框架设计 spring Boot Starter】

要求自愿降薪,员工内心普遍满意:“服从”是如何发生的?

脑极体

对比一下,你的简历是不是也写成了这样,能拿高薪才怪了

小Q

Java 学习 架构 面试 简历

重拳出击!平台经济反垄断,互联网巨头市值蒸发千亿

CECBC

小额贷款 反垄断

《我想进大厂》之Java基础夺命连环16问

艾小仙

Java 面试 编程语言

阿里首发MySQL“完美日记”,基础+优化+事务+集群+锁+主从复制+安全备份

Java架构追梦

Java MySQL 数据库 架构 面试

Java动态修改LOGGER日志级别

Zhendong

Java Arthas

腾讯云直播全解析,双11怎么买才不亏?

腾讯云音视频

腾讯云 阿里云 云直播 直播 视频

第七周作业

Geek_4c1353

极客大学架构师训练营

浅谈程序员的“内卷化”

数据社

架构训练营-week8-数据结构与算法,网络,IO

于成龙

极客大学架构师训练营 架构训练营

LeetCode题解:剑指 Offer 22. 链表中倒数第k个节点,使用栈,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

Pulsar Summit Asia 2020 | 场景案例论坛(下):多行业,多场景

Apache Pulsar

大数据 开源 Apache Pulsar

iptables 端口转发

田振宇

搞微服务用阿里开源的 Nacos 真香啊!

阿里巴巴 开源 编程

当Tomcat遇上Netty,我这一系列神操作,同事看了拍手叫绝

小Q

Java 学习 程序员 架构 面试

Java批量导入去除重复数据并返回结果,我差点就被放倒了

小Q

Java 学习 程序员 架构

MySQL 的 join 功能弱爆了?

程序员历小冰

MySQL postgres 多表join

【Mycat】Mycat核心开发者带你看尽Mycat三大核心配置文件!!

冰河

分布式数据库 中间件 mycat

当Nginx遇上Tomcat集群,又是一场负载均衡的爱恨情仇

小Q

nginx tomcat 学习 架构 面试

腾讯WeMap,一颗“孢子”的数智化之旅

脑极体

技术干货:Apache Pulsar 在移动云上的应用

Apache Pulsar

大数据 开源 云原生 Apache Pulsar

践行新基建,共建城市智能体,为数字经济发展提供新动能

CECBC

云计算 大数据

11.11 程序员的 1111 种死法

京东科技开发者

程序员 程序人生

面试蚂蚁金服,首战被MySQL惨虐,熬夜啃透这份阿里面经复盘一个月再战拿下P7offer

比伯

Java 程序员 架构 面试 阿里

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