写点什么

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

评论

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

AI大咖说-如何评价论文的创新性

AIWeker

人工智能 5月月更 论文写作

无死角窥探的焦虑:AI如何反击隐私侵占?

脑极体

【愚公系列】2022 年 05 月 二十三种设计模式(四)-原型模式(Prototype Pattern)

愚公搬代码

5月月更

HUAWEI永远滴神!华为顶级网络专家总结出了这份网络协议开源手册

Java架构追梦

华为 后端开发 网络协议、

《深入理解计算机系统》读书笔记——第二章(一)

如浴春风

5月月更

渗透必备:Kali中安装漏洞靶场Vulhub

喀拉峻

网络安全 漏洞 渗透 靶场

leecode上的代码到pycharm运行解决历程

武师叔

5月月更

四、应用高可用之容量设计

穿过生命散发芬芳

5月月更 容量设计

网络安全日常学习之渗透测试思路总结

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 漏洞挖掘

2022 之Python操作 Excel,xlrd 与 xlwt 模块一文掌握

梦想橡皮擦

5月月更

Nginx解决跨域问题

乌龟哥哥

5月月更

TypeScript 原始数据类型

Emperor_LawD

typescript 基础 5月月更

在线模拟解析Crontab表达式执行时间

入门小站

工具

架构实战营模块四作业

哈啰–J

跑赚项目-stepn后续-如何月入过万(33/100)

hackstoic

投资 web3

EasyRecovery2022最新版电脑数据恢复工具

茶色酒

数据恢复软件

springboot线程池的使用和扩展

程序员欣宸

Java 线程池 5月月更

GitHub霸榜月余的24万字Java面试手册,竟是阿里机密

Java架构追梦

Java 程序员 后端开发

Java并发JUC(java.util.concurrent)线程池

芝士味的椒盐

Java Java多线程 5月月更

开源字节技术架构

源字节1号

软件开发 后端开发

《敏捷软件测试》的作者将“Agile Testing”改为“Holistic Testing”

BY林子

软件测试 敏捷测试 持续测试

【JAVA秘籍功法篇-分布式事务】事务的实现原理

王老狮

分布式事务 CAP原理 BASE理论 ACID 事物的实现

架构实战营毕业总结

卡西毛豆静爸

架构训练营

模块9-设计电商秒杀系统

卡西毛豆静爸

#架构训练营

[Day35]-[二叉树]-二叉树的锯齿形层序遍历

方勇(gopher)

LeetCode 二叉树 数据结构算法

撸了一个Spring Boot + VUE 框架开发的分布式网盘系统「源码开源」

Java架构追梦

spring java面试 后端开发

算法:动态规划-斐波那契数列问题

正向成长

动态规划

在线Excel列提取导出工具

入门小站

工具

[Day35-02]-[二叉树]-求根节点到叶节点数字之和

方勇(gopher)

LeetCode 二叉树 数据结构算法

Java并发JUC(java.util.concurrent)集合不安全

芝士味的椒盐

Java Java集合框架 5月月更

linux手误rm可能不需要跑路

入门小站

Linux

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