虽然Python 3的官方文档努阴人们写同时支持Python 2和Python 3的代码,可是在一此状况这是合适的。尤为是你不能放弃支持Python 2.5及更早的版本时,由于Python 2.6引进了至关多的向前兼容。html
使相同的代码在更新的版本一样运行是有可能的,不过你开始进入Python 3文档中提到的“扭曲”的代码风格。我会使用一些技巧来作这些而且我在本章末尾提到的six模块会提供不少帮助。它甚至在一些比较大的项目中使用,可是我一般不推荐在在型项目中使用。对于小项目或者大项目的一部分,例如引导脚本,不使用2to3来支持老的Python版本是最好的解决的方式。python
Python 2.7对Python 3的兼容有一些小改进,但若是你想要在Python 2和Python 3下执行相同的代码,彷佛你必需要在未来的一段时间内支持Python 2.6。ide
不少你须要的修改将会被2to3处理,因此开始转换你的代码你事实上须要首先在你的代码上运行2to3而且确保你的代码能够在Python 3下运行。它一般很容易,或者至少在Python 3代码中引入Python 2兼容比在Python 2中引入Python 3代码更不单调。函数
一旦你有一个在Python 3下的项目,偿试在Python 2.6下运行他们。在这一步你能够执行出语法错误。他们应该只是来由print语句的变化。一旦你修复了他们,你能够修复剩下的错误,而后你最终能够在更早的Python版本作相同的事,若是你想一样支持他们。测试
语法错误的较多状况是print从语句变成了一个函数。这个简单的状况不是问题,你能够简单地在须要打印的文字周围加上括号。后面的例子可以在全部的Python版本正确地打印出相同的内容:编码
>>> print("This works in all versions of Python!") This works in all versions of Python!
然而,若是你使用了一些更高级的print特性However, 要么你会以一个语法错误结束,要么打印出的不是你但愿的。Python 2的后续命令在Python 3中变成了一个参数,因此若是你使用后续命令来取消打印以后的新行,在Python 3能够这样作print('Textto print', end=' '),而这在Python 2是一个语法错误。url
在Python 2.6下有一个__future__导入可让print成为一个函数。因此为了不一些语法错误及其余一些不一样,在你用到print()的文件应该要以from __future__ import print_function开始。这个__future__导入只能在Python 2.6及以后的版本工做,所为为了Python 2.5和更早的版本的你有两个选择。你要么把更复杂的print转换成更简单的,要么使用在Python 2和Python 3下都能工做的特殊print。spa
写一个你本身的print函数听起来比实际上更复杂。有一个技巧是使用sys.stdout.write()而且依照在函数中传入的参数作格式化。然而更加容易使用的是我将要本章结尾提到的six模块中的print_()函数。操作系统
若是你在你的异常处理里须要访问异常自己,你须要使用一个异常变量。在Python 2 这个语法是:.net
>>> try: ... a = 1/0 ... except ZeroDivisionError, e: ... print e.args[0] integer division or modulo by zero
然而,当你要捕捉不仅一个异常时这个语法是使人困惑的。你须要在异常值班表周围加上括号:
>>> try: ... a = 1/"Dinsdale" ... except (ZeroDivisionError, TypeError): ... print "You can't divide by zero or by strings!" You can't divide by zero or by strings!
若是你忘记了括号,那么只有ZeroDivisionError会被捕捉而且引发的异常储存在一个名为TypeError的变量里。那不是你指望有。所以,在Python 3,这个语法改为使用as来取代逗号,以免错误。当你捕捉多个异常时若是没有括号它也会给你一个语法错误:
>>> try: ... a = 1/'0' ... except (ZeroDivisionError, TypeError) as e: ... print(e.args[0]) unsupported operand type(s) for /: 'int' and 'str'
前面的语法也能够在Python 2.6中工做,Python 2.6能够同时使用旧的逗号语法和旧的as关键字语法。若是你须要支持比Python 2.6更低的Python版本而且你须要访问产生的异常,你能够经过exc_info()函数在全部版本作到:
>>> import sys >>> try: ... a = 1/'0' ... except (ZeroDivisionError, TypeError): ... e = sys.exc_info()[1] ... print(e.args[0]) unsupported operand type(s) for /: 'int' and 'str'
另外一个不一样之处在于Exception是不长的迭代器。在Python 2给异常的参数是能够迭代遍历异常或者下标异常。
>>> try: ... f = 1/0 ... except ZeroDivisionError, e: ... for m in e: ... print m ... print e[0] integer division or modulo by zero integer division or modulo by zero
在Python 3你须要使用异常的args属性来替代:
>>> try: ... f = "1" + 1 ... except TypeError as e: ... for m in e.args: ... print(m) ... print(e.args[0]) Can't convert 'int' object to str implicitly Can't convert 'int' object to str implicitly
一个message属性已经被添加到了Python 2.5的内置异常。然而它在Python 2.6已经被弃用并在Python 3移除。当使用-3标志时Python 2.6和Python 2.7为这个警报你。
大改变之一是标准库的整改,结果你在这步获得的常见错误是导入错误。绕过它是很容易的。你能够简单地偿试从Python 3的位置丑话而且在失败的时候从Python 2的位置导入。对于被重命名的模块,你只要把他们导入成新的名字。
>>> try: ... import configparser ... except ImportError: ... import ConfigParser as configparser
一些新的模块合并了多个旧的模块,若是你须要的东西是来自多个旧模块那上上面的状况将不能工做。你也能够不用那些有子模块的模块来这些。import httplib as http.client是一个语法错误。urllib和urllib2不只仅是合并,也整合进了几个子模块。因此你须要分别导入每个你用到的模块名。这经意味着你一样须要修改你的代码,在Python 2你能够像这样检索一个网页:
>>> import urllib >>> import urlparse >>> >>> url = 'http://docs.python.org/library/' >>> parts = urlparse.urlparse(url) >>> parts = parts._replace(path='/3.0'+parts.path) >>> page = urllib.urlopen(parts.geturl())
在2to3转换以后它看起来是这样:
>>> import urllib.request, urllib.parse, urllib.error >>> import urllib.parse >>> >>> url = 'http://docs.python.org/library/' >>> parts = urllib.parse.urlparse(url) >>> parts = parts._replace(path='/3.0'+parts.path) >>> page = urllib.request.urlopen(parts.geturl())
是的,urllib.parse会被引用两次而且urllib.error虽然被引用可是没有被使用。这就是固定是如何作的,它作了不少额外的努力,因此它导入的比须要的更多。咱们须要修复代码使咱们直接使用的名字来取代模块的位置,因此能同时在Python 2和Python 3下同时运行的版本最终看起来像这样:
>>> try: ... from urllib.request import urlopen ... from urllib.parse import urlparse ... except ImportError: ... from urlparse import urlparse ... from urllib import urlopen ... >>> url = 'http://docs.python.org/library/' >>> parts = urlparse(url) >>> parts = parts._replace(path='/3.0'+parts.path) >>> page = urlopen(parts.geturl())
在Python 3的整数处理上有两个大变化。第一个是int和long类型被合并。这意味着你不能再经过添加L结尾来指定整数是long类型。1L在Python 3是一个语法错误。
若是你必定要在Python 2中使用长整形整数而且兼容Python 3,下面的代码能够作到。它在Python 3定义了一个跟int类同样的long变量,而且它以后能够被用于明显地确保整数是一个长整形。
>>> import sys >>> if sys.version_info > (3,): ... long = int >>> long(1) 1L
另外一个变化是八进制字面量语法的变化。在Python 2一个0开始的数字是八进制,其中若是你打算用零来开始一个数字时会产生混淆。在Python 3这个语法换成了更不混淆的0o,类似的0x用于十六进制数字。用0开始一个数字是一个语法错误,这防止了你错误地使用旧式八进制语法。
八进制几乎只用于Unix下的权限设置,但这又是一个至关常见的任务。有一个能同时在Python 2和Python 3工做的简单方式:使用十进制或者十六进制值并把这个八进制值放进一个命令里:
>>> f = 420 # 在八进制是644, 'rw-r--r--'
咱们在不用2to3同时支持Python 2和Python 3时遇到的最棘手问题是处理二进制数据,这一点也不奇怪,就像使用2to3时同样。当大家选择不用2to3时这个问题通Unicode问题产生了更多了麻烦,当使用2to3来支持Python 2和Python 3时2to3会把Unicode文字转换成直接的字符串文字。不使用2to3我就没有这种愉快而且由于Un文字u''在Python 3已经消失咱们须要找到一种方式来讲咱们想要一种能在全部Python版本下工做的Unicode字符串。
这里,只支持Python 3.3可让事情更容易不少,由于在Python 3.3u''文字回来了。在这种状况下,你几乎能够乎略这部分。
可是若是你须要支持Python 3.1或者3.2,要作这个的最好方式是写一个像在常见迁移问题中的b()函数那样只是使用Unicode字符串代替二进制字节的Unicode字符串产生函数。给这个函数的自然名字固然是u()。而后咱们使用b()来代替b''文字,而且使用u()来取代u''文字。
import sys if sys.version_info < (3,): import codecs def u(x): return codecs.unicode_escape_decode(x)[0] else: def u(x): return x
这将会在Python 2中返回一个unicode对象:
>>> from makeunicode import u >>> u('GIF89a') u'GIF89a'
可是它在Python 3返回一个字符串对象:
>>> from makeunicode import u >>> u('GIF89a') 'GIF89a'
这里我使用unicode_escape编码,由于若是你用和函数指定编码不一样的编码来保存文件时其它的编码会失败。unicode_escape在输入字符和保存文件上多作了一些工做,可是它会在不一样的Python版本以及不一样的操做系统平台上工做。
unicode_escape编码会转换输入unicode字符的全部版本的方式。'\x00' 语法,'\u0000' 甚至'\N{name}'语法:
>>> from makeunicode import u >>> print(u('\u00dcnic\u00f6de')) Ünicöde >>> print(u('\xdcnic\N{Latin Small Letter O with diaeresis}de')) Ünicöde
若是你只须要支持Python 2.6或者以后的版本,也可使用from __future__ import unicode_literals。这能够把文件中的全部字符串文字转成Unicode文字:
>>> from __future__ import unicode_literals >>> type("A standard string literal") <type 'unicode'> >>> type(b"A binary literal") <type 'str'>
在Python 2下全部带有__future__导入及u()函数的二进制数据类型被叫着str而且文本类型被称为unicode,然而在Python 3下他被称为bytes和str。
绕过这个的最好办法是定义根据不一样的Python版本两个变量:text_type 和binary_type,而后再测试一次这些变量。
>>> from __future__ import unicode_literals >>> import sys >>> if sys.version_info < (3,): ... text_type = unicode ... binary_type = str ... else: ... text_type = str ... binary_type = bytes >>> isinstance('U\xf1ic\xf6de', text_type) True
对于处理二进制数据你可使用在《常见的迁移问题》中讨论的相同的技巧。
Python 2和Python 3之间有不少不少更不一样寻常而且有时候很微妙的不一样。虽然这里提到的技巧也能适合大多数这些不一样意,我仍是强烈推荐Benjamin Peterson的模块“six”[1]。它包含一个使用于检查Python版本的PY3常量,而且它包含前面提到的b()和u()函数,并且u()函数没有指定一个编码,因此你只能使用ASCII字符。它也包含不少像text_type和 binary_type这样有帮助的常量以及能同时在Python 2和Python 3下同时工做的print_()函数。
它还包含大量重组标准库的的导入,因此取代这章开始中的try/except结构的是你能够用导入six模块中的模块来代替。值得注意的是不支持重组的urllib和urllib2模块,因此你仍是须要使用try/except导入技巧。
six模块甚至包含不寻常问题的助理,例如使用已经被更名的元类和函数的属性。虽然它须要Python 2.4及以后的版本,但若是须要的话你能够在更早的Python版本下使用许多它里的技巧。
若是你试图不转换支持Python 2和Python 3,你必定会发现它颇有帮助。
附注:
[1] | http://pypi.python.org/pypi/six |
本文地址:http://my.oschina.net/soarwilldo/blog/522927
在湖闻樟注:
原文http://python3porting.com/noconv.html