Python 2.x 系列在 2020 年 1 月已正式结束,并且在 2020 年 4 月之后不再受支持,但是将代码转换为 Python 3 比你想象的要容易。上周末,我花了一个晚上将一个 3D 渲染器(及其对应的 Python for Qt/PySide 版本)的前端代码转换为 Python 3,回顾起来,这出奇地简单,尽管在重构过程中它看起来相对无望。转换过程可能看起来有点像迷宫,你做的每一个更改都会揭示你需要做的十几个更多更改。
你可能想或可能不想进行转换,但是——无论是由于你拖延太久,还是你依赖于一个模块,除非你转换,否则该模块将不会被维护——有时你只是别无选择。如果你正在寻找一个简单的任务来开始你对开源的贡献,那么将 Python 2 应用转换为 Python 3 是一个为产生简单但有意义的印象的好方法。
无论你将 Python 2 代码重构为 Python 3 的原因是什么,这都是一项重要的工作。以下是以清晰的方式处理此任务的三个步骤。
1. 运行 2to3
在过去的几年中,Python 附带了一个名为 2to3 的脚本,该脚本为你完成从 Python 2 到 Python 3 的大部分转换。自动地。并且你已经安装了它(无论你是否意识到)。
这是一个用 Python 2.6 编写的简短代码片段
#!/usr/bin/env python
# -*- coding: utf-8 -*-
mystring = u'abcdé'
print ord(mystring[-1])
运行 2to3 脚本
$ 2to3 example.py
RefactoringTool: Refactored example.py
--- example.py (original)
+++ example.py (refactored)
@@ -1,5 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-mystring = u'abcdé'
-print ord(mystring[-1])
+mystring = 'abcdé'
+print(ord(mystring[-1]))
RefactoringTool: Files that need to be modified:
RefactoringTool: example.py
默认情况下,2to3 仅打印将旧 Python 代码升级到 Python 3 标准所需的更改。输出是一个可用的补丁,你可以使用它来更改你的文件,但是让 Python 为你执行此操作更容易,使用 --write(或 -w)选项
$ 2to3 -w example.py
[...]
RefactoringTool: Files that were modified:
RefactoringTool: example.py
2to3 脚本不只适用于单个文件。你可以在 Python 文件的整个目录上运行它,无论是否使用 --write 选项,以处理目录及其子目录中的所有 *.py 文件。
2. 使用 Pylint 或 Pyflakes
发现代码缺陷在 Python 2 中运行没有问题,但在 Python 3 中运行效果不佳是很常见的。由于这些缺陷无法通过转换语法来修复,因此它们会通过 2to3 而未更改,但是一旦你尝试运行代码,它们就会失败。
为了检测此类问题,你可以使用像 Pylint 这样的应用程序或像 Pyflakes 这样的工具(或 flake8 包装器)。我更喜欢 Pyflakes,因为与 Pylint 不同,它忽略了代码风格中的偏差。虽然 Python 的“美观性”通常被誉为其优势之一,但在将别人的代码从 2 移植到 3 时,将风格和功能视为两个单独的错误是优先考虑的问题。
这是来自 Pyflakes 的示例输出
$ pyflakes example/maths
example/maths/enum.py:19: undefined name 'cmp'
example/maths/enum.py:105: local variable 'e' is assigned to but never used
example/maths/enum.py:109: undefined name 'basestring'
example/maths/enum.py:208: undefined name 'EnumValueCompareError'
example/maths/enum.py:208: local variable 'e' is assigned to but never used
此输出(与 Pylint 的 143 行输出相比,其中大多数是对缩进的抱怨)清楚地显示了你应该修复的代码中的问题。
这里最有趣的错误是第一个错误,在第 19 行。这有点误导,因为你可能会认为 cmp 是一个从未定义的变量,但 cmp 实际上是 Python 2 中的一个函数,在 Python 3 中不存在。它被包装在一个 try 语句中,因此这个问题很容易被忽视,直到 try 结果未产生变得明显。
try:
result = cmp(self.index, other.index)
except:
result = 42
return result
当应用程序作为 Python 2 代码库维护时,以及当你决定移植它时,存在无数不再存在或已更改的函数的示例。PySide(2) 绑定已更改,Python 函数已消失或已转换(例如,imp 到 importlib),等等。当你遇到它们时,逐个修复它们。即使由你来重新实现或替换那些缺失的函数,但到目前为止,大多数这些问题都是已知的并且 有据可查。真正的挑战更多在于捕获错误而不是修复错误,因此请使用 Pyflakes 或类似的工具。
3. 修复损坏的 Python 2 代码
2to3 脚本使你的代码符合 Python 3 标准,但它只知道 Python 2 和 3 之间的差异。它通常无法进行调整以考虑 2010 年以来的库更改,但自那时以来已进行了重大修订。你必须手动更新该代码。
例如,此代码在 Python 2.6 的时代显然可以工作
class CLOCK_SPEED:
TICKS_PER_SECOND = 16
TICK_RATES = [int(i * TICKS_PER_SECOND)
for i in (0.5, 1, 2, 3, 4, 6, 8, 11, 20)]
class FPS:
STATS_UPDATE_FREQUENCY = CLOCK_SPEED.TICKS_PER_SECOND
像 2to3 和 Pyflakes 这样的自动化工具无法检测到问题,但是 Python 3 不会将 GAME_SPEED.TICKS_PER_SECOND 视为有效语句,因为被调用的函数从未明确声明。调整代码是在面向对象编程中的一个简单练习
class CLOCK_SPEED:
def TICKS_PER_SECOND():
TICKS_PER_SECOND = 16
TICK_RATES = [int(i * TICKS_PER_SECOND)
for i in (0.5, 1, 2, 3, 4, 6, 8, 11, 20)]
return TICKS_PER_SECOND
class FPS:
STATS_UPDATE_FREQUENCY = CLOCK_SPEED.TICKS_PER_SECOND()
你可能会倾向于通过用构造函数(一个 __init__ 函数来设置默认值)替换 TICKS_PER_SECOND 函数,使其更简洁,但是这将更改所需的调用从 CLOCK_SPEED.TICKS_PER_SECOND() 到仅 CLOCK_SPEED(),这可能会或可能不会在代码库的其他地方产生影响。如果你很了解代码,那么你可以更好地判断多少
多少更改是必需的,以及多少更改只是令人愉悦的,但总的来说,我更喜欢假设我做的每一个更改都不可避免地需要对项目中的每个其他文件至少进行三个更改,所以我尝试在其现有结构中工作。
不要停止相信
如果你正在移植一个非常大的项目,有时会开始感觉遥遥无期。在你看到一个有用的错误消息不是关于滑过脚本和 linters 的 Python 2 怪癖之前,可能需要很长时间,一旦你到达那个点,你就会开始怀疑从头开始会更容易。好的一面是,你(大概)知道你正在移植的代码库在 Python 2 中工作(或曾经工作过),并且一旦你进行调整,它将在 Python 3 中再次工作;这只是一个转换问题。
一旦你完成了前期工作,你将拥有一个 Python 3 模块或应用程序,并且可以重新开始定期维护(以及那些使 Pylint 感到满意的样式更改)!
评论已关闭。