【Python专题(二)】Python二三事
- 2020 年 3 月 5 日
- 筆記
Python2 终于在2020年1月1日官方正式退休了,虽然消息大家提前很久就知道了,但是笔者发现周围依然有很多python2的代码并没有迁移到python3上。所以可以预见的是未来一段时间内,我们还是会面临很多python2和python3反复切换的情况。有时候处理这种python版本问题很让人恼火,你清楚问题在哪里,你也知道你要一个一个去找去定位去改,一定能解决问题,但是你懒得这么做,除非明天是deadline。
有没有个办法可以优雅地解决这个问题呢。看完文章就知道啦。
不知道大家有没有注意到,两三年前用python的时候python2和python3简直是势不两立,python3调python2的package很难不报错。但是近两年python3调python2的package几乎不会报错。原因有两个,第一就是早期的很多package本身就是纯python2写的,完全没有做python3的兼容,但是后来的很多package在写的时候就考虑了python2和python3的兼容问题,会分别写一个python2的版本和一个python3的版本。第二个原因就是随着python2和python3兼容性问题日益凸显,很多专门解决兼容性问题的package,诸如future,past,six等,也日渐成熟,这极大的简化了两个版本互相兼容的工作,有时甚至只需要加一行代码就可以让python3支持python2的项目。
01
python2和python3内建函数(builtins)的区别
这种内建函数的区别就是两个版本的原生差别了,上文介绍的 future
package 就是专门为了解决这种问题的。
1.print函数
这个应该是大家最熟悉也是最常见的区别。
python2中的print函数是不需要括号的:
print "hello world"
注:现在的python2.7也支持加括号。
python3中的print函数必须加括号:
print("hello world")
所以print这里在python3改到python2的情况下是不需要修改的。
2.除法运算
这个也是比较重要的一个区别。python2中的整数除法默认向下取整,而python3中的整数除法默认返回浮点数。
Python2:
print(4/3) # output: 1
Python3:
print(4/3) # output: 1.3333...
3.编码问题
python有2种字符串类型,分别是 str
和 unicode
,str类型变量中保存的是ASCII数据,而Unicode类型变量中保存的是Unicode数据。为了便于理解,这里需要展开一下ASCII和Unicode分别是什么。ASCII是一种编码方式,以一个字节(Byte)为单位保存一个字符,共256种状态。对于英文来说,ASCII已经足够使用了,但是考虑到中文、拉丁文等其他语言,256种状态就不够用了,因此就开始用两个字节为单位保存一个字符,这样可以表示65536种字符,足够覆盖世界上所有符号了,由于这种编码统一了世界上所有的符号,因此叫做Unicode编码。除此之外,你可能听过还有一种编码叫做UTF-8,它可以理解为是一种Unicode的优化方案,因为英文并不需要两个字节的Unicode,为了避免内存的浪费,UTF-8会先识别符号类别,根据符号类别决定每个字符读取1个字节还是2个字节的数据。这样,对于每个英文字符,UTF-8就读取1个字节数据,对于中文等其他字符就读取2个字节数据。另外还有一些专门为中文设计的编码例如GB2312,GB18030等,在一些特定情况也会用的到。
说了这么多,python2和python3编码问题到底在哪呢?问题就在于python2和python3在字符串处理的设计思路不同,python2中会默认把所有Unicode读成1个字节然后用ASCII解码,因此默认情况下,ASCII编码的英文字符不会出现任何问题,但是其他字符,例如中文,在读取的时候就会出现 UnicodeDecodeError
的错误(相信写过python2的同学一定被这玩意困扰过),过去的解决方案便是在代码第一行加上 # -*- coding: utf-8 -*-
让编译器默认使用UTF-8编码。但是python3不会对Unicode做任何解码,保留Unicode字符,然后用默认的解码方式(一般为UTF-8)来解码。
来看个例子:
Python2:
a = "你" print repr(a) # 输出: xe4xbdxa0
Python3:
a = "你" print(repr(a)) # 输出: 你
Python2和python3兼容方案,在代码开头导入:
from __future__ import unicode_literals
这样就会把python2中所有的字符串改成Unicode,而不会默认用ASCII来解码,从而解决python2中的字符串解码问题。
4.引用问题
python2和python3的引用的默认方式也有所不同。python2默认相对路径导入package,而python3默认绝对路径导入package。换言之,python2在import时的默认搜索顺序是:builtin package(python内建库)、当前路径下的库(自己写的文件)、第三方库(安装的第三方库);而python3在import时的默认搜索顺序是:builtin package(python内建库)、第三方库(安装的第三方库)、当前路径下的库(自己写的文件)。这种默认方式的不同也会在项目中导致一些引用问题。
Python2和python3兼容解决方案,让python统一默认绝对路径导入package:
from __future__ import absolute_import
5.迭代器(iterator)区别
python2和python3显式地调用迭代器的方式有所不同。在python2中定义迭代器的方法是类中的 next()
方法,但是python3中定义迭代器的方法则是类中的 __next__()
方法。python2在调用迭代器输出下一个元素时,是调用对象的 next()
方法也就是 obj.next()
,而python3在调用迭代器输出下一个元素时,用 next(obj)
。这里借用future官网文档中的一个例子来说明:
Python2:
class Upper(object): def __init__(self, iterable): self._iter = iter(iterable) def next(self): # Py2-style return self._iter.next().upper() def __iter__(self): return self itr = Upper('hello') assert itr.next() == 'H' # Py2-style assert list(itr) == list('ELLO')
Python3:
class Upper(object): def __init__(self, iterable): self._iter = iter(iterable) def __next__(self): # Py3-style iterator interface return next(self._iter).upper() # builtin next() function calls def __iter__(self): return self itr = Upper('hello') assert next(itr) == 'H' # compatible style assert list(itr) == list('ELLO')
python2和python3兼容方案,在代码前加:
from builtins import object
或者
from future.utils import implements_iterator
6.其他问题
上述的5种兼容性问题只是一部分笔者认为出现频率较高的情况。其实python2和python3之间还有很多细微的不同都可能影响你代码的运行结果和质量,例如字典有序性的改变(python2中的字典是无序的,python3中的字典是有序的)、metaclass的区别、以及map,range等函数的区别等等。由于篇幅和精力有限,本文不再详细探讨,大家可以根据文末的参考文献自行查阅。
02
python2和python3标准库使用的区别
除了一些内建函数的区别,还有很多标准库的使用在python2和python3中略有不同。我这里列举一些我会经常遇到的问题来说明。
1.urllib
urllib是python中使用非常广泛的一个用于网络协议解析,资源请求的标准库,与此同时,它也是最难做到python2和python3兼容的标准库。如果你还没开始写这部分代码,那可以考虑不用这个库, Requests
(http://python-requests.org)也许是更好的选择。但是如果你是在修改别人写好的代码,那只能硬着头皮改下去了。这个库在2和3版本里文件结构发生了较大的改变,从引用就可以看出来:
Python2:
from urlparse import urlparse from urllib import urlencode from urllib2 import urlopen, Request, HTTPError
Python3:
from urllib.parse import urlparse, urlencode from urllib.request import urlopen, Request from urllib.error import HTTPError
Python2和python3兼容的解决方案:
from future.standard_library import install_aliases install_aliases() from urllib.parse import urlparse, urlencode from urllib.request import urlopen, Request from urllib.error import HTTPError
2.pickle
pickle是用来保存和读取数据结构,文件的标准库。在python2中这个标准库叫做 cPickle
:
import cPickle
在python3中这个标准库更名为pickle:
import pickle
Python2和python3解决方案:
import six.moves.cPickle as pickle
3.其他module
本文所列的两个package只是笔者经常会遇到的情况,因此仅对它们做了特别说明。实际上两个版本之间还有很多标准库使用方法不同,由于篇幅和精力的限制,不在此做详细说明,感兴趣可以在文末的参考文献中进一步查阅。
03
Python2/3自动转换
到现在为止,你应该对python2和python3兼容问题有了一个大概的认识了。接下来,我们来具体看看,如何用这些成熟的工具优雅地解决兼容性问题。我们将你可能遇到的场景分为三种,第一,将python3代码改成python2;第二,将python2代码改成python3;第三,自己写的项目同时支持python2和python3。
首先来看场景一:将python3代码改成python2.
这种需求乍看上去很奇怪,但是笔者确实遇到了这种情况。有个package是很早前用python2写的,属于之前我们说的完全没有考虑兼容问题的那一类package,但是这个package又是我做项目必须要用到的,而不幸的是,项目中其他的代码都是用python3写的。因此我必须要在项目中解决兼容问题——把python3的代码改到python2然后用python2运行项目。你可能会问,为什么不把python2的包改成支持python3呢?因为那个package不是我们项目写的,我们不是维护者,改起来可能会出现不可预知的问题,所以最好还是改动自己的代码。解决方案:
pip install future # 安装future pasteurize -w mypy3module.py # 将文件改为可同时支持python2和python3
场景二:将python2代码迁移到python3.
这种需求应该是非常常见的一种了,随着python2彻底成为历史,如果你手上还有不兼容python3的python2代码,确实应该考虑把它迁移到python3了。幸运的是,我们不需要跑去源代码中一一对应python2和python3的区别然后逐一搜索去修改。future
提供了一个非常方便的工具—— futurize
来帮你实现这个需求:
pip install future # 安装future futurize --stage1 -w test/*.py # 将test路径下的所有.py文件从python2改到python3. futurize --stage2 -w test/*.py # 使得test路径下的所有.py文件同时支持python2/3
更详细的使用教程参见:futurize(https://python-future.org)
当然这两种全自动的代码转换解决方案都有一定局限性,因为实际中代码情况千变万化,为了确保成功迁移,你应该每做一步转换就对代码的行为进行测试,测试中遇到的一些问题需要手动修改解决,当测试没问题后,再进行下一步转换。
场景三:写出python2/3兼容的package
这种场景在python2已然退休的今天应该已经成为伪需求了,但是为了让我们这里讨论的场景尽可能完备,还是把它加进来吧。在 python2和python3的区别
这部分中,我们给出的python2和python3兼容的解决方案其实就是答案了。总体的思路如下:首先你需要了解两个版本中哪些地方有区别,然后利用future,six等兼容性解决package去统一这些区别,这样最后写出的package就是python2/3同时兼容的了。
04
结语
本篇文章主要介绍了python2和python3的兼容性问题(区别)、对应的解决方案以及py2/py3代码自动转换工具。这里面很多区别和场景都是根据我自己所遇到的情况总结的,所以肯定有不完善的地方,大家如果有好的建议和意见也欢迎下方留言评论。希望本文可以或多或少地帮助到你。最后很想分享一句老友送我的话与大家共勉:计算机的很多东西看似高深但是并没有那么难理解,因为这个领域的所有东西都是人来定义的,你很容易找到其中的逻辑来帮助你学习。
05
参考文献
[1] Six: Python 2 and 3 Compatibility Library
[2] Cheat Sheet: Writing Python 2-3 compatible code
[3] Automatic conversion to Py2/3
[4] Unicode HOWTO