阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

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

评论

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

开源应用中心|这款小而强大的开源博客程序,简直让人爱不释手!

开源

2021智博会全国区块链大赛暨首届“星火杯”区块链应用大赛正式启动

浪潮云

云计算,

花了3个小时解决了和异地女朋友一起看电影的需求(内附源码)

ZEGO即构

android 音视频 一起看电影

ONNX 浅析:如何加速深度学习算法工程化?

拍乐云Pano

人工智能 深度学习 音视频

自动化驱动的高可用网络:爱奇艺B2网络流量自动调度系统建设实践

爱奇艺技术产品团队

流量

区块链是如何运作的、是如何防止被篡改的?

CECBC

财经违规自媒体集体扑街,必须打击违规自媒体账号

石头IT视角

Alibaba 80k Star《Java面试突击手册》(全彩版)开源

Java 程序员 架构 面试 计算机

量化交易APP开发,量化交易系统源码

13530558032

Java 17 正式发布,Oracle 宣布从 JDK 17 开始正式免费,Java 迈入新时代

Java 编程 源码 架构

【Vuex 源码学习】第五篇 - Vuex 中 Mutations 和 Actions 的实现

Brave

源码 vuex 9月日更

使用分布式锁的正确姿势

张大彪

分布式锁

软件测试工程师应该怎样规划自己

程序员阿沐

程序员 职业规划 软件测试 自动化测试 经验分享

围绕低代码开发存在的三个误解

低代码小观

程序员 低代码 企业管理 低代码开发平台 应用开发

【IM开源推荐】前微信技术专家打造的开源的即时通讯组件OpenIM

OpenIM

Alibaba iOS 工程架构腐化治理实践

阿里巴巴终端技术

ios 研发效能 客户端 架构治理

观测云品牌正式亮相,携手中国信通院共推国内可观测性概念与技术发展!

观测云

40 个 SpringBoot 常用注解:让生产力爆表

Java 面试 框架 Spring Boot 2

重磅发布!百度飞桨获2021全球智博会产品金奖

百度大脑

人工智能

【上汽零束SOA】云管端一体化SOA软件平台系列介绍之六:数字生态篇

SOA开发者

自动驾驶 SOA 智能汽车 OTA

【OpenIM原创】IM服务端docker、源码、集群部署 非常实用

OpenIM

集齐海外主流云平台,EMQ X Cloud 正式支持 Google Cloud Platform 部署

EMQ映云科技

IoT Google Cloud AIOT 云 原生云 CTO

WorkPlus即时通讯软件,满足政企局域网办公需求

WorkPlus

Android如何用代码执行shell命令

Changing Lin

9月日更

相约重庆,2021 DEMO CHINA创新中国峰会倒计时六天

创业邦

【云计算】从事云计算运维可以考取哪些证书?

行云管家

云计算 阿里云 运维 华为云 云运维

等保三级多久测评一次?每年都要测评吗?

行云管家

网络安全 信息安全 等保

北鲲云SaaS平台为生物制药研发进程带来“加速度”

北鲲云

【上汽零束SOA】云管端一体化SOA软件平台系列介绍之六:数字生态篇

SOA开发者平台

软件 SOA 生态 汽车 OTA

让全链路压测变得更简单!Takin2.0重磅来袭!

TakinTalks稳定性社区

到底什么是区块链?是咋样的运营原理

CECBC

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