Python 是在 1980 年代末开发的,并于 1991 年首次发布。Python 的名字灵感来自于英国喜剧团体蒙提·派森,Python 被构想为命令式通用编程语言 ABC 的继任者。在最初的版本中,Python 已经包括了异常处理、函数和具有继承性的类。
本教程将指导您在将代码从 Python 2 迁移到 Python 3 时应考虑的最佳实践和注意事项,以及您是否应该维护与两个版本兼容的代码。
Python 2 于 2000 年发布,标志着一种更加透明和包容的语言开发过程。它包括了更多的编程特性,并在其发展过程中添加了更多功能。
Python 3 被视为 Python 的未来,是目前正在开发中的语言版本。Python 3 于 2008 年末发布,解决并修正了内在的设计缺陷。然而,由于 Python 3 与 Python 2 不兼容,Python 3 的采用速度较慢。
Python 2.7 于 2010 年发布,是 2.x 系列的最后一个版本。Python 2.7 的目的是通过在两者之间提供一定程度的兼容性,使 Python 2.x 用户更容易将功能迁移到 Python 3。
您可以通过阅读我们的教程“Python 2 vs Python 3: 实际考虑”来了解更多关于 Python 版本和选择使用哪个版本的信息。
要迁移到 Python 3,或者同时支持 Python 2 和 Python 3,您应确保您的 Python 2 代码完全兼容 Python 2.7。
许多开发人员已经在专门使用 Python 2.7 代码,但重要的是要确认任何仅受早期版本支持的内容在 Python 2.7 中能够正常工作,并且符合 Python 2.7 的风格。
确保您的代码在 Python 2.7 中运行良好尤为重要,因为它是仍在维护和接收错误修复的唯一 Python 2 版本。如果您使用的是 Python 2 的早期版本,您将不得不解决不再受支持且不再接收错误修复的代码中遇到的问题。
此外,一些使您更容易迁移代码的工具,如寻找编程错误的 Pylint 包,不支持早于 2.7 版本的 Python。
需要牢记的是,尽管 Python 2.7 目前仍在得到支持和维护,但它最终将会终止生命周期。PEP 373 详细说明了 Python 2.7 的发布计划,并在撰写本文时将其终止日期标记为 2020 年。
创建测试用例可能是迁移 Python 2 到 Python 3 代码的重要部分。如果您维护多个 Python 版本,还应确保您的测试套件总体上具有良好的覆盖率,以确保每个版本仍然按预期工作。
作为测试的一部分,您可以将交互式 Python 用例添加到所有函数、方法、类和模块的文档字符串中,然后使用内置的 doctest
模块验证它们是否按预期工作。
除了 doctest
,您还可以使用 coverage.py
包来跟踪单元测试覆盖率。该工具将监视您的程序,并记录已执行的代码部分以及可能已执行但未执行的部分。coverage.py
可以在命令行打印报告,也可以提供 HTML 输出。通常用于衡量测试的有效性,显示哪些代码部分通过测试得到执行,哪些没有。
需要记住的是,您并不是在追求 100% 的测试覆盖率 — 您要确保覆盖任何令人困惑或不寻常的代码。根据最佳实践,您应该追求 80% 的覆盖率。
了解 Python 2 和 Python 3 之间的差异将确保您能够利用 Python 3 中可用或将来可用的新功能。
我们的“Python 2 vs Python 3”指南介绍了这两个版本之间的一些关键差异,您也可以查阅官方 Python 文档以获取更详细的信息。
在开始移植和迁移时,有几个语法更改可以立即实施。
print
Python 2 中的 print
语句在 Python 3 中已更改为 print()
函数。
Python 2 | Python 3 |
---|---|
print "Hello, World!" |
print("Hello, World!") |
exec
Python 2 中的 exec
语句在 Python 3 中已更改为允许显式局部变量和全局变量的函数。
Python 2 | Python 3 |
---|---|
exec code |
exec(code) |
exec code in globals |
exec(code, globals) |
exec code in (globals, locals) |
exec(code, globals, locals) |
/
和 //
Python 2 使用 /
操作符进行地板除法,Python 3 引入 //
进行地板除法。
Python 2 | Python 3 |
---|---|
5 / 2 = 2 |
5 / 2 = 2.5 |
5 // 2 = 2 |
要在 Python 2 中使用这些操作符,需要从__future__
模块中导入 division
:
from __future__ import division
了解更多关于整数除法的内容。
raise
在 Python 3 中,使用参数来引发异常需要使用括号,而且字符串不能作为异常使用。
Python 2 | Python 3 |
---|---|
raise Exception, args |
raise Exception |
raise Exception(args) |
|
raise Exception, args, traceback |
raise Exception(args).with_traceback(traceback) |
raise "Error" |
raise Exception("Error") |
except
在 Python 2 中,列出多个异常比较困难,但在 Python 3 中已经改变。
需要注意的是,在 Python 3 中 except
需要显式使用 as
。
Python 2 | Python 3 |
---|---|
except Exception, variable: |
except AnException as variable: |
except (OneException, TwoException) as variable: |
def
在 Python 2 中,函数可以接受元组或列表等序列。在 Python 3 中,这种解包已经被移除。
Python 2 | Python 3 |
---|---|
def function(arg1, (x, y)): |
def function(arg1, x_y): x, y = x_y |
expr
Python 2 中的反引号语法已经不存在。在 Python 3 中使用 repr()
或 str.format()
。
Python 2 | Python 3 |
---|---|
x = `355/113` |
x = repr(355/113): |
字符串格式化语法从 Python 2 到 Python 3 有所改变。
Python 2 | Python 3 |
---|---|
"%d %s" % (i, s) |
"{} {}".format(i, s) |
"%d/%d=%f" % (355, 113, 355/113) |
"{:d}/{:d}={:f}".format(355, 113, 355/113) |
学习如何在 Python 3 中使用字符串格式化。
class
在 Python 3 中不需要在 class
中声明 object
。
Python 2
class MyClass(object):
pass
Python 3
class MyClass:
pass
在 Python 3 中,使用 metaclass
关键字来设置元类。
Python 2
class MyClass:
__metaclass__ = MyMeta
class MyClass(MyBase):
__metaclass__ = MyMeta
Python 3
class MyClass(metaclass=type):
pass
class MyClass(MyBase, metaclass=MyMeta):
pass
有两个主要工具可以用来自动将代码更新到 Python 3 并保持与 Python 2 兼容:future 和 modernize。这两个工具的行为略有不同:future
用于使 Python 3 的习惯用法和最佳实践在 Python 2 中存在,而 modernize
则旨在使用 Python six
模块创建 Python 2/3 子集以提高兼容性。
使用这些工具来处理代码重写的细节可以帮助您识别和纠正潜在的问题和歧义。
您可以在您的单元测试套件上运行该工具,以直观地检查和验证代码,并确保自动进行的修改是准确的。一旦测试通过,您就可以转换您的代码。
从这里开始,您可能需要进行一些手动修订,特别是针对上面部分中 Python 2 和 3 之间的更改。
利用 future
,您应该考虑在每个 Python 2.7 模块中添加以下 import
语句:
from __future__ import print_function, division, absolute_imports, unicode_literals
虽然这也会导致重写,但它将确保您的 Python 2 代码与 Python 3 语法一致。
最后,您可以使用 pylint
包来识别代码中的任何其他潜在问题。该包包含数百个单独的规则,涵盖了可能出现的广泛问题,包括 PEP 8 风格指南规则以及使用错误。
您可能会发现您的代码中有一些构造可能会让 pylint
和用于自动迁移的工具产生混淆。如果无法简化这些构造,您将需要使用彻底的单元测试用例。
如果你要为多个 Python 版本维护你的代码,你需要保持警惕,尽可能经常地通过持续集成来运行和重新运行你的单元测试套件(而不是手动操作)来开发代码。
如果你在维护 Python 2 和 3 的兼容性时使用了 six
包,你需要为你的测试使用多个环境。
你可以考虑使用一个环境管理工具,比如 tox
包,它将使用不同的 Python 版本检查你的包安装,在每个环境中运行测试,并作为持续集成服务器的前端。
重要的是要记住,随着越来越多的开发者和社区关注于 Python 3,这门语言将变得更加精致,并与程序员不断变化的需求保持一致,对 Python 2.7 的支持将会减少。如果你决定为 Python 2 和 Python 3 维护你的代码库的版本,随着时间的推移,你可能会发现在前者上会遇到越来越多的困难,因为它将会收到更少的错误修复。
值得一提的是,值得看看那些将 Python 2 移植到 Python 3 的项目,包括像将 chardet
移植到 Python 3 的案例研究。