目录

前言

最近 Python 之父 Guido van Rossum(龟爷)终于在 Python 官方邮件组落实了 Python 2.7 的终焉之日(EOL)。

Let’s not play games with semantics. The way I see the situation for 2.7 is that EOL is January 1st, 2020, and there will be no updates, not even source-only security patches, after that date. Support (from the core devs, the PSF, and python.org) stops completely on that date. If you want support for 2.7 beyond that day you will have to pay a commercial vendor. Of course it’s open source so people are also welcome to fork it. But the core devs have toiled long enough, and the 2020 EOL date (an extension from the originally annouced 2015 EOL!) was announced with sufficient lead time and fanfare that I don’t feel bad about stopping to support it at all.

一言以蔽之,说的是 Python 2.7 的 EOL 日期最终确定为 2020 年 1 月 1 日,之后不会有任何更新,包括源码的安全补丁。

So,还没有兼容或移植到 Python 3.x 的程序,或许已经到了该提上日程的时候了。在下文中,我们就「选择 Python 2 还是 3 ?两者的主要区别是什么?如何编写兼容两者的代码?」这 3 个经常在面试中出现的问题来展开。

Python 2 or 3 ?

Python 3 被钦定为 Python 的未来,于 2008 年末发布,是目前正在开发的版本。旨在解决和修正 Python 2 遗留的设计缺陷、清理代码库冗余、追求有且仅有一种最佳实践方式来执行任务等问题。

起初,由于 Python 3 不能向后兼容的事实,导致了用户采用缓慢,对初学者不友好等问题。但在 Python 社区的努力和决绝态度下,截至龟爷发出邮件之前,已经有了 21903 个 Packages 可以支持 Python 3.5,其中包括了绝大多数最受欢迎的封装库,与此同时也有越来越多的封装库(e.g. Django、Numpy)表示其新版本将不再支持 Python 2。

可见时至今日的 Python 3 已成气候,正如龟爷在邮件中所说:「停止支持 Python 2,我完全没觉得有啥糟糕的」。所以笔者认为,如果你喜欢 Python 并希望能够掌握它,那么你完全没有必然去纠结于学习 2 还是 3 的问题。而且当我们在讨论 Python 2.x 的问题时,实际上更多的是在讨论 Python 2.7。

Python 2.7 于 3.0 之后的 2010 年 7 月 3 日发布,计划作为 2.x 的最后一个版本。Python 2.7 的历史任务在于通过提供 2 和 3 之间的兼容性措施,使 Python 2.x 的用户更容易将代码移植到 Python 3.x 上。那么如果你希望自己的代码能够兼容两个不同的版本,首先你起码要让代码能够正常的运行在 Python 2.7 上。

注:下文使用 P2 表示 Python 2.7;使用 P3 表示 Python 3.x

不同与兼容

__future__ 模块是我们首先需要了解的,该模块最主要的作用是支持在 P2 中导入那些在 P3 才生效的模块和函数。是一个非常优秀的兼容性工具库,在下文中给出的许多 兼容技巧 实例都依赖于它。

特性 在此版本可选 在此版本内置 效果
nested_scopes 2.1.0b1 2.2 PEP 227:静态嵌套作用域
generators 2.2.0a1 2.3 PEP 255:简单生成器
division 2.2.0a2 3.0 PEP 238:除法操作符改动
absolute_import 2.5.0a1 3.0 PEP 328:Imports 多行导入与绝对相对路径
with_statement 2.5.0a1 2.6 PEP 343:with 语句
print_function 2.6.0a2 3.0 PEP 3105:print 语句升级为函数
unicode_literals 2.6.0a2 3.0 PEP 3112:Bytes 类型

(__future__ 功能列表)

统一不等于语法

P2 支持使用 <>!= 表示不等于。

P3 仅支持使用 != 表示不等于。

兼容技巧:统一使用 != 语法

统一整数类型

P2 中整数类型可以细分为短整型 int 和长整型 long。

P3 废除了短整型,并统一使用 int 表示长整型(不再有 L 跟在 repr 后面)。

兼容技巧

# Python 2 only
k = 9223372036854775808L
# Python 2 and 3:
k = 9223372036854775808 # Python 2 only
bigint = 1L
# Python 2 and 3
from future.builtins import int
bigint = int(1)

统一整数除法

P2 的除法 / 符号实际上具有两个功能:

  • 当两个操作数均为整型对象时,进行的是地板除(截除小数部分),返回整型对象;
  • 当两个操作数存在至少一个浮点型对象时,进行的是真除(保留小数部分),返回浮点型对象。

P3 的除法 / 符号仅仅具有真除的功能,而地板除的功能则交由 // 来完成。

兼容技巧

# Python 2 only:
assert 2 / 3 == 0
# Python 2 and 3:
assert 2 // 3 == 0
“True division” (float division): # Python 3 only:
assert 3 / 2 == 1.5
# Python 2 and 3:
from __future__ import division # (at top of module)

统一缩进语法

P2 可以混合使用 tab 和 space 两种方式来进行缩进(1 个 tab == 8 个 space),但实际上这一特性并非所有 IDE 都能够支持,会因此出现同样的代码无法跨 IDE 运行的情况。

P3 统一使用 tab 作为缩进,如果 tab 和 space 同时存在,就会触发异常:

TabError: inconsistent use of tabs and spaces in indentation.

兼容技巧:统一使用 tab 作为缩进。

统一类定义

P2 同时支持新式类(object)和老式类。

P3 则统一使用新式类,并且只有使用新式类才能应用多重继承。

兼容技巧:统一使用新式类。

统一字符编码类型

P2 默认使用 ASCII 字符编码,但因为 ASCII 只支持数百个字符,并不能灵活的满足非英文字符,所以 P2 同时也支持 Unicode 这种更强大的字符编码。不过,由于 P2 同时支持两套字符编码,就难免多出了一些标识和转换的麻烦。

而 P3 统一使用 Unicode 字符编码,这节省了开发者的时间,同时也可以轻松地在程序中输入和显示更多种类的字符。

兼容技巧:在所有的字符串赋值中均使用前缀 u,或引入 unicode_literals 字符模块。

# Python 2 only
s1 = 'The Zen of Python'
s2 = u'きたないのよりきれいな方がいい\n' # Python 2 and 3
s1 = u'The Zen of Python'
s2 = u'きたないのよりきれいな方がいい\n' # Python 2 and 3
from __future__ import unicode_literals # at top of module s1 = 'The Zen of Python'
s2 = 'きたないのよりきれいな方がいい\n'

统一导入模块的路径搜索方式

P2 导入一个模块时首先会搜索当前目录(cwd),若非,则搜索环境变量路径(sys.path)。这一特性时常给开发者带来困扰,相信大家都曾经碰到过,尤其当自定义模块与系统模块重名的时候;

为了解决这个问题,默认的 P3 仅会搜索环境变量路径,当你需要搜索自定义模块时,你可以在包管理模式下将项目路径加入到环境变量中,然后再使用绝对路径和相对路径(以 . 开头)的方式来导入。

兼容技巧:统一使用绝对路径进行自定义模块导入。

修正列表推导式的变量作用域泄露

P2 的列表推倒式中的变量会泄露到全局作用域,例如:

import platform

print('Python', platform.python_version())
i = 1
print('before: I = %s' % i)
print('comprehension: %s' % [i for i in range(5)])
print('after: I = %s' % i) # OUT
Python 2.7.6
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 4

P3 则解决了这个问题,列表推倒式中的变量不再泄露到全局作用域。

import platform

print('Python', platform.python_version())
i = 1
print('before: i =', i)
print('comprehension:', [i for i in range(5)])
print('after: i =', i) # OUT
Python 3.4.1
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 1

修正非法比较操作异常

P2 能够对两个数据类型并不相同的对象进行比较。

import platform

print('Python', platform.python_version())
print("[1, 2] > 'foo' = ", [1, 2] > 'foo')
print("(1, 2) > 'foo' = ", (1, 2) > 'foo')
print("[1, 2] > (1, 2) = ", [1, 2] > (1, 2)) # OUT
Python 2.7.6
[1, 2] > 'foo' = False
(1, 2) > 'foo' = True
[1, 2] > (1, 2) = False

不过,这种看似方便的特性,实际上却是一个定时炸弹,因为你无法唯一的确定到底是什么原因导致的返回值为 False(可能是数据比较、也可能是数据类型不一致)。

P3 则对其进行了修正,如果比较操作数类型不一致时,会触发 TypeError 异常。

兼容技巧:永远不要比较数据类型不一致的对象。

统一抛出异常语法

P2 同时支持新旧两种异常触发语法:

raise IOError, "file error"   # Old
raise IOError("file error") # New

P3 则统一使用新异常触发语法,否则会触发 SyntaxError 异常:

raise IOError("file error")

兼容技巧

### 抛出异常
# Python 2 only:
raise ValueError, "dodgy value"
# Python 2 and 3:
raise ValueError("dodgy value") ### 使用 traceback 抛出异常
# Python 2 only:
traceback = sys.exc_info()[2]
raise ValueError, "dodgy value", traceback
# Python 3 only:
raise ValueError("dodgy value").with_traceback()
# Python 2 and 3: option 1
from six import reraise as raise_
# or # from future.utils import raise_
traceback = sys.exc_info()[2]
raise_(ValueError, "dodgy value", traceback)
# Python 2 and 3: option 2
from future.utils import raise_with_traceback
raise_with_traceback(ValueError("dodgy value")) ### 异常链处理
# Setup:
class DatabaseError(Exception):
pass
# Python 3 only
class FileDatabase:
def __init__(self, filename):
try:
self.file = open(filename)
except IOError as exc:
raise DatabaseError('failed to open') from exc
# Python 2 and 3:
from future.utils import raise_from
class FileDatabase:
def __init__(self, filename):
try:
self.file = open(filename)
except IOError as exc:
raise_from(DatabaseError('failed to open'), exc)

统一异常处理语法

P2 实现异常处理也能够支持两种语法。

try:
let_us_cause_a_NameError
except NameError, err:
# except NameError as err:
print err, '--> our error message'

P3 的异常处理则强制要求使用 as 关键字的方式。

try:
let_us_cause_a_NameError
except NameError as err:
print(err, '--> our error message')

兼容技巧:统一使用 as 关键字的异常处理方式。

统一输入函数

P2 支持 raw_input 和 input 两个输入函数,区别在于前者仅能返回 String 类型对象,后者则支持返回数字和字符串两种数据类型对象,并且当输入为表达式时,会隐式调用 eval 函数返回其执行结果。显然的,使用 input 是更加灵活的写法。

所以 P3 统一的使用了 input 函数进行输入处理。

兼容技巧:统一使用 input 内置函数。

# Python 2 only:
input("Type something safe please: ") # Python 2 and 3
from future.builtins import input
eval(input("Type something safe please: "))

统一输出函数

P2 中的 print 即是关键字又是内置函数。print 'Hello world!' 为一条语句,print('Hello world!') 则为一次函数调用。

P3 统一使用 print 函数进行输出操作,其原型如下,这一改变让 P3 的输出处理变得更加简洁、强大而优雅,通过实参的传递就能替代 P2 中繁复的代码实现。

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

兼容技巧

### 单行打印单个 String
# Python 2 only:
print 'Hello'
# Python 2 only:
print 'Hello' ### 单行打印多个 String
# Python 2 only:
print 'Hello', 'Guido'
# Python 2 and 3:
from __future__ import print_function # (at top of module)
print('Hello', 'Guido') ### 输出重定向
# Python 2 only:
print >> sys.stderr, 'Hello'
# Python 2 and 3:
from __future__ import print_function
print('Hello', file=sys.stderr) ### 换行打印
# Python 2 only:
print 'Hello',
# Python 2 and 3:
from __future__ import print_function
print('Hello', end='')

统一文件操作函数

P2 支持使用 file 和 open 两个函数来进行文件操作。

P3 则统一使用 open 来进行文件操作。

兼容技巧:统一使用 open 函数。

# Python 2 only:
f = file(pathname)
# Python 2 and 3:
f = open(pathname)

统一列表迭代器生成函数

P2 支持使用 range 和 xrange 两个函数来生成可迭代对象,区别在于前者返回的是一个列表类型对象,后者返回的是一个类似生成器(惰性求值)的迭代对象,支持无限迭代。所以当你需要生成一个很大的序列时,推荐使用 xrange,因为它不会一上来就索取序列所需的所有内存空间。如果只对序列进行读操作的话,xrange 方法效率显然会更高,但是如果要修改序列的元素,或者往序列增删元素的话,那只能通过 range 方法生成一个 list 对象了。

P3 则统一使用 range 函数来生成可迭代对象,但其实 P3 的 range 更像是 P2 的 xrange。所以在 P3 中如果你想得到一个可以被修改的列表对象,你需要这么做:

list(range(1,10))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

兼容技巧:统一使用 range 函数

# Python 2 only:
for i in xrange(10**8):
... # Python 2 and 3: forward-compatible
from future.builtins import range
for i in range(10**8):
... # Python 2 and 3: backward-compatible
from past.builtins import xrange
for i in xrange(10**8):
...

统一迭代器迭代函数

P2 中支持使用内置函数 next 和迭代器对象的 .next() 实例方法这两种方式来获取迭代器对象的下一个元素。所以,在实现自定义迭代器对象类时,必须实现 .next() 实例方法:

# Python 2 only
class Upper(object):
def __init__(self, iterable):
self._iter = iter(iterable)
def next(self): # Py2-styface iterator interface
return self._iter.next().upper()
def __iter__(self):
return self itr = Upper('hello')
assert itr.next() == 'H' # Py2-style
assert list(itr) == list('ELLO')

但在 P3 中统一了使用 next 内置函数来获取下一个元素,如果试图调用 .next() 方法则会触发 AttributeError 异常。所以,在 P3 中实现自定义迭代器所要实现的是 __next__ 特殊方法。

兼容技巧

# Python 2 and 3: option 1
from future.builtins import object 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') # Python 2 and 3: option 2
from future.utils import implements_iterator @implements_iterator
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'
assert list(itr) == list('ELLO')

Python 2 和 3 的区别及兼容技巧的更多相关文章

  1. Python - Python2与Python3的区别、转换与兼容

    区别 Python2.x与Python3.x版本区别:http://www.runoob.com/python/python-2x-3x.html 示例解读Python2和Python3之间的主要差异 ...

  2. python中// 和/有什么区别

    python中// 和/有什么区别 通常C/C++中,"/ " 算术运算符的计算结果是根据参与运算的两边的数据决定的,比如: 6 / 3 = 2 ; 6,3都是整数,那么结果也就是 ...

  3. python 3 与python 2连接mongoDB的区别

    本文出自:https://www.cnblogs.com/2186009311CFF/p/11852010.html 好久前机缘巧合见识过量化投资,然而堵在了用python连接MongoDB数据库上, ...

  4. ava、Python和PHP三者的区别

    Java.Python和PHP三者的区别 2017年07月15日 22:09:21 书生_AABB 阅读数:18994   版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  5. Python 2 和 3 的区别记录

    Python 2 和 3 的区别记录 print 2:关键字,可以 print a,也可以 print(a) 3:内置函数,必须带(),print(a) reload() 2:内置函数,可以直接使用 ...

  6. Python中__repr__和__str__区别

    Python中__repr__和__str__区别 看下面的例子就明白了 class Test(object): def __init__(self, value='hello, world!'): ...

  7. Java Web开发和Python Web开发之间的区别

    今天的文章讨论了Java Web开发和Python Web开发之间的区别.我不鼓励我们在这里从Java Web迁移到Python Web开发.我只是想谈谈我的感受.它不一定适合所有情况,仅供我们参考. ...

  8. python,pycharm,anaconda之间的区别与联系 - python基础入门(2)

    Python环境配置-Pycharm下载/Anaconda安装 中我们已经完成了 Pycharm 和Anaconda 的安装.可能对于刚接触的小伙伴还是比较懵逼的,一会python一会Anaconda ...

  9. python 2 和3 的区别

    python 2 和3 的区别 Python2 Python3 打印 print " " print() 输入 raw_input() input() 范围 range/xrang ...

随机推荐

  1. css3 伪类以及伪元素的特效

    菱形          

  2. laravel5.8 源码分析(1) Route

    https://learnku.com/docs/laravel/5.8 源码路径 vendor\laravel\framework\src\Illuminate\Routing\Router.php ...

  3. linux 下 SpiderMonkey 1.7.0 编译和安装

    wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.7.0.tar.gz tar xf js-1.7.0.tar.gz cd js/src make ...

  4. 007-zabbix Server 4.0 监控TCP的12种状态

    大家对TCP三次握手比较熟悉了,都知道当发生DOSS攻击时,客户端发送SYN给服务端后,服务端响应SYN+ACK,此时客户端就不回应服务端ACK啦(如果正常建立三次握手客户端会回应ACK,表示三次握手 ...

  5. hive模拟数据

    人员表 id,姓名,爱好,住址 1,小明1,lol-book-movie,beijing:mashibing-shanghai:pudong 2,小明2,lol-book-movie,beijing: ...

  6. tensorflow 屏蔽 Log

    pip install alfred-py 在代码中加入 from alfred.dl.tf.common import mute_tf mute_tf()

  7. VS2015加载错误的解决办法

    在开始菜单中找到vs2015 的 vs2015 开发人员命令提示. 在命令提示符中输入 devenv /setup 然后回车,大概几分钟后可以执行完成 重启vs2015 发现问题解决.

  8. WPF 多个选项卡TabControl 页面分离

    此项目源码下载地址:https://github.com/lizhiqiang0204/TabControl-page-separation 每个页面的按键处理事件直接对应该页面下的cs文件 Main ...

  9. 安装 mysql odbc连接器

    下载地址: https://dev.mysql.com/downloads/connector/odbc/ 可以选择旧版本的下载 一. 配置数据源 1. 安装后如果找不到软件可以按  win键 后 输 ...

  10. HashMap的底层实现以及解决hash值冲突的方式

    class HashMap<K,V> extends AbstractMap<K,V> HashMap  put() HashMap  get() 1.put() HashMa ...