如何尽量让Python 2.x与Python 3.x兼容?
有很多方法可以在Python 2.x中使用Python 3.x的特性,这样将来把Python 2.x的代码转换成Python 3.x就会变得简单。一个例子就是把print
语句替换成print()
函数:
>>> from __future__ import print_function
有没有什么列表或者资源,可以给我们一些想法,如何让Python 2.x的代码尽可能接近Python 3.x?
你能给出其他一些有用的导入或定义的例子,让Python 2.x看起来和行为上更像Python 3.x吗?
假设我们手头有最新的Python 2.x(目前是2.7.2,我相信)。
6 个回答
在《迁移到Python 3》这本书里,有整整一章专门讲这个问题。而且别忘了查看附录,里面列出了语言之间的差异,并提供了一些解决办法,可以让你同时支持这两种语言。
你可能会想用six库,虽然其实不使用它也可以实现相同的功能。
你可以看看这个链接:将Python代码迁移到3.0。虽然这个内容主要是讲如何把代码迁移到Python 3.0,但它其实也能回答你类似的问题,只不过你不需要完全按照它的方法去做。
我正在完成一个大约5000行的去重备份程序(http://stromberg.dnsalias.org/~strombrg/backshift/),这个程序可以在CPython 2.[567]、CPython 3.[0123](3.3版本仍在测试中)、Pypy 1.7和Jython的最新版本上运行。我也试过IronPython,但它和其他的差别很大——没有标准库,所以没法用backshift。哦,对了,它可以用Cython来加速最内层的循环,或者用psyco,但在32位系统上,pypy的速度更快。
总之,我发现要写出在2.x和3.x上都能正常运行的代码,我只需要做到以下几点:
1) 在2.x和3.x中,print(variable)的用法是一样的,但print(variable1, variable2)就不一样了。对2.x来说,print(variable)的意思是“计算这个括号里的表达式,然后用print语句打印出结果”。而在3.x中,print(variable)的意思是“对这个结果调用print函数”。所以,print('abc %d %d' % (1, 2))在两者中都能正常工作,因为它是一个单一的结果,而且两者都能理解字符串格式化的%操作符。
2) 避免使用八进制常量。与其写0755,不如写(7*64 + 5*8 + 5)。
3) 在两者中进行二进制输入输出时,我使用了我的bufsock模块。http://stromberg.dnsalias.org/~strombrg/bufsock.html 我会用os.open打开一个文件,然后用bufsock包装它(或者使用模块中的rawio类)。在2.x中,这会返回一个以8位字符字符串编码的字节串。在3.x中,这会返回一个bytes对象,类似于小整数的列表。然后我只需在两者之间传递,必要时用“isinstance(foo, str)”来区分这两者。我这样做是因为对备份程序来说,字节就是字节——我不想因为编码问题而影响数据的可靠保存,而并不是所有的编码都能很好地进行往返转换。
4) 在处理异常时,避免使用“as”关键字。可以用这样的方式:
try:
self.update_timestamp()
except (OSError, IOError):
dummy, utime_extra, dummy = sys.exc_info()
if utime_extra.errno == errno.ENOENT:
5) 在从2.x过渡到3.x的过程中,很多模块的名称都被改了。所以可以尝试在一个空模块中导入其中一个,像这样:
try:
from anydbm import *
except ImportError:
from dbm import *
...这会在一个独立的模块中出现,命名为例如adbm.py。然后每当我需要一个键值存储时,我就会导入adbm,而不是直接导入2.x或3.x所需的两个不同模块。接着,我会对除了这个小模块adbm.py之外的所有内容进行pylint检查——以及pylint不喜欢的其他类似模块。我的想法是尽可能对所有内容进行pylint检查,除了这个小模块外,每个模块都有一个例外。
6) 设置自动单元测试和系统测试,确保它们在2.x和3.x上都能运行,并且频繁在至少一个2.x解释器和一个3.x解释器上进行测试,这非常有帮助。我也经常对我的代码运行pylint,尽管只检查2.5.x的合规性——因为我在pylint支持3.x之前就开始了这个项目。
7) 我设置了一个小的“python2x3”模块,里面有一些常量和可调用对象,以便让生活更轻松:http://stromberg.dnsalias.org/svn/python2x3/trunk/python2x3.py
8) 在2.5中,b''字面量不工作,虽然在2.[67]中有点效果。与其尝试预处理什么的,不如设置一个constants_mod.py,里面有很多通常在3.x中是b''字面量的东西,并将它们从简单字符串转换为2.x或3.x的“bytes”类型。所以它们在模块导入时只转换一次,而不是在运行时反复转换。如果你的目标是2.[67]及以上,可能有更好的方法,但当我开始时,Pypy项目仅兼容2.5,而Jython至今仍然如此。
9) 在2.x中,长整型有一个L后缀。在3.x中,所有整型都是长整型。所以我尽量避免使用长整型常量;2.x会在必要时将整型提升为长整型,所以这对大多数情况来说都没问题。
10) 拥有多个python解释器进行测试非常有帮助。我构建了2.[567]和3.[0123],并将它们存放在/usr/local/cpython-x.y/中,方便测试。我还把一些Pypy和Jython放在/usr/local中,同样是为了方便测试。拥有一个自动化CPython构建的脚本非常有价值。
我相信这些都是我为了在一个非简单项目中获得高度可移植的python代码库所需的各种折腾。唯一遗漏的一点是,我并不打算使用unicode对象——这可能是其他人更适合评论的内容。
希望这些对你有帮助。