github: Writing Python 2-3 compatible code, Open in Jupyter
Python2/3兼容是可行的,许多库为了只维护一套代码,也是利用一些兼容库做到Python2/3兼容。虽然有2to3, 3to2, 2to6之类的代码转换工具,但是基本没什么人用,转出来也有许多问题。Python社区一般还是手工写同时兼容Python2/3的代码(这点和transpiler满天飞的JS社区很不同)。
python3出来的时候,python的设计者们当然也考虑过代码之间的兼容问题。许多为为兼容性设计的功能可以通过future这个包来导入。例如:
from __future__ import print_function
使用python3的print函数,禁用python2的print语句。
from __future__ import unicode_literals
像python3一样,字符串字面量的类型为文本(python2中的unicode,python3中的str),而不是字节(python2中的str,python3中的bytes)。
from __future__ import absolute_import
参见PEP 328 -- Imports: Multi-Line and Absolute/Relative
from __future__ import division
像python3一样,int除以int得float,而不像Python2那样是整除
导入这四个功能后,很多语法就和Python3中差不多了。
只用__future__是不够的,Python2/3之间还有别的差异。用six这个第三方模块可以解决这个问题(这里应该批评一下Python设计者们,这本应该是他们考虑的问题)。
例如,Python2和Python3中字符串类型名字不同,six可以把它们变成了统一的第三种形式(知乎怎么不支持表格啊):
| 字符串类型 | 文本(unicode) | 字节(二进制) |
|---|---|---|
| Python2 | unicode: u"abc" | str(默认): "abc" |
| Python3 | str(默认): "abc" | bytes: b"abc" |
| six | six.text_type | six.binary_type |
例如,python2原来这么写:
if isinstance(xxx, unicode):
...
改成这样就可以同时兼容Python2/3:
import six
...
if isinstance(xxx, six.text_type):
...
six还能处理包名或者函数名不同的情况,例如Python2中的raw_input函数在Python3中变成了input函数。则可以从six中导入同时兼容Python2/3的input函数:
from six.moves import input
虽然利用__future__和six,可以写出同时兼容Python2/3的程序。但我觉得这样看上去未免会使程序变得丑陋,而且很多Python3的新特性还没法用了(例如asyncio, Type Hinting, f-string等)。所以还是希望Python3能够普及。
在下面代码中,我们想导入urlopen()函数,在Python2中,他同时存在与urllib和urllib2中(我们使用后者),
在Python3中,他被集成到了urllib.request中,而你的方案是要既能在2.x和3.x中正常工作:
try:
from urllib2 import urlopen
from urllib import urlencode
from urllib import quote
from urllib import unquote
from urlparse import urlparse
import urllib2 as request
import cookielib
except ImportError:
from urllib.request import urlopen
from urllib.parse import urlencode
from urllib.parse import quote
from urllib.parse import unquote
from urllib.parse import urlparse
import urllib.request as request
import http.cookiejar as cookielib
出于对内存的保护,也许你对iterator(Python3)版本的zip()更加有兴趣,在Python2中,iterator版本是itertools.izip()。
这个函数在Python3中被重命名替换成了zip()。如果你使用迭代版本,导入语句也非常直白:
try:
from itertools import izip as zip
except ImportError:
pass
try:
# Python2
from ConfigParser import ConfigParser
except ImportError:
# Python3
from configparser import ConfigParser
cp = ConfigParser()
另一个列子是看来来并不怎么优雅的StringIO类,在Python2中,纯Python版本是StringIO模块,意味着访问的时候是通过StringIO.StringIO,同样还有一个更为快速的C语言版本,位于cStringIO.StringIO,不过这取决你的Python安装版本,你可以优先使用cStringIO然后是StringIO(如果cStringIO不能用的话)。在Python3中,Unicode是默认的string类型,但是如果你做任何和网络相关的操作,很有可能你不得不用ASCII/字节字符串来操作,所以代替StringIO,你要io.BytesIO,为了达到你想要的,这个导入看起来有点丑:
try:
from io import BytesIO as StringIO
except ImportError:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
import sys
global PY2
if sys.version_info.major == 2:
# Python2
PY2 = True
else:
# Python3
PY2 = False
# do something
static = '用花言巧语编造的谎言,常常使那些不了解实际情况的人上当。'
if PY2:
static = static.decode('utf-8')
print('PY2')
else:
print('PY3')
pass
print(static)
print(type(static))
import sys
global PY3
if sys.version_info.major == 3:
# Python3
PY3 = True
else:
# Python2
PY3 = False
# do something
static = '用花言巧语编造的谎言,常常使那些不了解实际情况的人上当。'
if PY3:
print('PY3')
else:
static = static.decode('utf-8')
print('PY2')
pass
print(static)
print(type(static))
python2 print 输出是 str 和 Unicode, python3 输出是 bytes 和 str(Unicode) 问题,解决方法:
import sys
# 将 unicode -> str
if sys.version_info.major == 2:
str = unicode
# Python3中的chr()不仅仅支持 0~255的整数 -> ASCII字符,直接支持了更为适用的 0x4E00 - 0x9FA6的整数 -> Unicode字符
if sys.version_info.major == 3:
unichr = chr