如何尽可能地使Python 2.x与Python 3.x兼容?

37

有许多方法可以在 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)。

2
该问题的答案可能会有所帮助,它与编写尽可能接近Python 3.x语法的Python 2.7代码有关。 - Bora Caglayan
2
@BoraCaglayan:是的,标题几乎与这个问题完全相同 :) 但不幸的是,该链接是关于Django应用程序的(意味着存在代码库,您不得破坏该应用程序)。我的问题是关于新创建的应用程序,您不需要担心例如有很多使用“print”语句而不是函数的代码。我相信这消除了很多问题。 - Tadeck
@Tadeck:不,print函数是非常微不足道的问题。大多数问题都是相同的,而且更难解决。使用Django并不能真正避免任何兼容性问题。 - Lennart Regebro
@LennartRegebro:我的意思是,这个问题是关于如何在Python 2.x中创建一个尽可能接近Python 3.x的新应用程序。我认为使用Django会带来一些问题,因为你已经有了不兼容Python 3.x编码方式的代码,因此你的选择有限。如果我错了,请纠正我。 - Tadeck
将自己限制在2.7(甚至包括2.6)是好的。将自己限制在3.3或更高版本也同样可以! 3.3重新引入了u''语法! - Robert Siemer
6个回答

22

我正在完成一个大约5000行的去重备份程序(http://stromberg.dnsalias.org/~strombrg/backshift/),可以运行在CPython 2.[567]、CPython 3.[0123](3.3仍处于alpha 0阶段)、Pypy 1.7和Jython trunk上。我也尝试了IronPython,但它是一种非常不同的东西——它没有标准库,因此没有backshift支持。哦,它可以使用Cython作为其最内层循环或者psyco - 但是pypy比这两个都快,特别是在32位系统上。

总之,我发现要编写能够在2.x和3.x上运行得同样良好的代码,我只需要做到以下几点:

1)print(variable)在2.x和3.x上的表现相同。而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)在任一版本中进行二进制I/O,我都使用了我的bufsock模块。http://stromberg.dnsalias.org/~strombrg/bufsock.html 我会os.open一个文件,并用bufsock进行包装(或者使用模块中的rawio类)。在2.x上,这将返回一个作为8位字符字符串编码的字节字符串。在3.x上,这将返回一个bytes对象,其行为类似于小整数列表。然后我只需传递一个或另一个,根据需要使用"isinstance(foo, str)"进行测试以区分两者。我这样做,是因为对于备份程序而言,字节就是字节 - 我不想在保存数据时搞乱编码,而且并非所有编码都能很好地回路。

4)在处理异常时,避免使用“as”关键字。使用EG:

  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 *

...这将出现在一个单独的模块中,命名为EG adbm.py。 然后,每当我需要键值存储时,我会导入adbm,而不是直接导入2.x或3.x所需的两个不同的东西。 然后,我会对除了那个小小的模块adbm.py之外的所有内容进行pylint检查,以及像它一样的pylint不喜欢的东西。 这个想法是尽可能对所有内容进行pylint检查,在一个小模块中例外处理“一切都必须pylint”的规则。

6)设置自动单元测试和系统测试非常有帮助,这些测试在2.x和3.x上运行,然后经常在至少一个2.x解释器和至少一个3.x解释器上进行测试。 我也经常针对我的代码运行pylint,尽管只使用一个检查2.5.x兼容性的pylint——我在pylint获得3.x支持之前就开始了该项目。

7)我设置了一个小的“python2x3”模块,其中包含一些常量和可调用项,以使生活更轻松:http://stromberg.dnsalias.org/svn/python2x3/trunk/python2x3.py

8)b''文字在2.5中不起作用,尽管它们在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中,所有整数都是long类型。 所以我尽量避免使用长整型常量; 2.x会根据需要将整数提升为长整型,因此这对于大多数东西似乎很好。

10) 拥有一堆Python解释器非常有帮助。 我构建了2.[567]和3.[0123]并将它们存储在/usr/local/cpython-x.y/中以进行方便测试。 我还将一些Pypy和Jython放在/usr/local中,以方便测试。 编写自动化CPython构建脚本非常有价值。

我相信这些都是我在一个非常复杂的项目中需要采取的所有姿势。 上面提到的一个重要的遗漏是,我没有尝试使用Unicode对象-这是其他人可能更有资格发表评论的事情。

希望对您有所帮助


1
嘿,你知道 sys.exc_info() 这个技巧会让 PyPy 崩溃吗? - fijal
不,我不是。谢谢你提醒我。在Pypy中需要什么来修复它? - dstromberg
它真的有效吗?这似乎可以运行:
尝试: .... dummy = 1/0 .... except: .... dummy,foo,dummy = sys.exc_info() .... 打印foo .... 整数除零
- dstromberg
它可以工作,但 sys.exc_info() 会使整个帧栈出现。这对于调试器或类似工具(因为它是设计用来这样做的)来说是可以接受的,但对于仅需要异常对象的情况来说,这是一个巨大的过度杀伤力。JIT 将中止跟踪和编译,并退出汇编程序。 - fijal
1
对于关注 sys.exc_info() 的人:如果您仅支持 Python 2.6+ 和没有旧版本,则实际上无需避免使用 except ... as ... - Tadeck

8
你应该查看将Python代码迁移到3.0版。虽然它是针对迁移编写的,但它实际上回答了同样的问题;你只是不需要走完整个过程。

谢谢。我对您提供的链接中的第二个选项特别感兴趣:"_使代码在Python 2和Python 3中都能无修改地运行_。"这个选项最接近我想要实现的目标。 - Tadeck

8

这个问题在《Python 3迁移指南》中有一个完整的章节。此外,不要错过附录,其中列出了语言差异和支持两种语言的解决方法。

您可能想使用six库,尽管也可以在没有它的情况下完成。


5

2

我有一个Python 2.7脚本模板的开头:

from __future__ import division, print_function
from future_builtins import ascii, filter, hex, map, oct, zip

2
这远远不足以实际使用。而且 future_builtins 在 Python 3 中不存在,因此这段代码实际上无法在 Python 3 上运行。 - Lennart Regebro
1
把 future_builtins 模块保留在 Python 3 标准库中,这样从一个公共代码库支持 Python2 和 Python3 版本会更加方便实用,不是吗? - pmav99

1

既然还没有提到:我发现这个速查表对于这个确切的目的非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接