Python 2.7.x 与 Python 3.x 的主要差异
因这学期负责Python课程的助教,刚开始上机试验的几节课,有很多同学用 Python3.4 的编译器编译 Python 2.7 的程序而导致不通过。Python 2.7.x 和 Python 3.x 版本并非完全兼容。
许多 Python 初学者想知道他们应该从 Python 的哪个版本开始学习。对于这个问题我的答案是 “你学习你喜欢的教程的版本,然后检查他们之间的不同。” 但如果你并未了解过两个版本之间的差异,个人推荐使用 Python 2.7.x 版本,毕竟大部分教材等资料还是用Python 2.7.x来写的。
但是如果你开始一个新项目,并且有选择权?我想说的是目前没有对错,只要你计划使用的库 Python 2.7.x 和 Python 3.x 双方都支持的话。尽管如此,当在编写它们中的任何一个的代码,或者是你计划移植你的项目的时候,是非常值得看看这两个主要流行的 Python 版本之间的差别的,以便避免常见的陷阱。
本文翻译自:《Key differences between Python 2.7.x and Python 3.x》
__future__
模块
Python 3.x 介绍的 一些Python 2 不兼容的关键字和特性可以通过在 Python 2 的内置 __future__
模块导入。如果你计划让你的代码支持 Python 3.x,建议你使用 __future__
模块导入。例如,如果我想要 在Python 2 中表现 Python 3.x 中的整除,我们可以通过如下导入
|
|
更多的 __future__
模块可被导入的特性被列在下表中:
feature | optional in | mandatory in | effect |
---|---|---|---|
nested_scopes | 2.1.0b1 | 2.2 | PEP 227: Statically Nested Scopes |
generators | 2.2.0a1 | 2.3 | PEP 255: Simple Generators |
division | 2.2.0a2 | 3.0 | PEP 238: Changing the Division Operator |
absolute_import | 2.5.0a1 | 3.0 | PEP 328: Imports: Multi-Line and Absolute/Relative |
with_statement | 2.5.0a1 | 2.6 | PEP 343: The “with” Statement |
print_function | 2.5.0a2 | 3.0 | PEP 3105: Make print a function |
unicode_literals | 2.5.0a2 | 3.0 | PEP 3112: Bytes literals in Python 3000 |
(Source: https://docs.python.org/2/library/future.html)
|
|
print
函数
很琐碎,而 print
语法的变化可能是最广为人知的了,但是仍值得一提的是: Python 2 的 print 声明已经被 print()
函数取代了,这意味着我们必须包装我们想打印在小括号中的对象。
Python 2 不具有额外的小括号问题。但对比一下,如果我们按照 Python 2 的方式不使用小括号调用 print
函数,Python 3 将抛出一个语法异常(SyntaxError
)。
Python 2
|
|
run result:
Python 2.7.6
Hello, World!
Hello, World!
text print more text on the same line
Python 3
|
|
run result:
Python 3.4.1
Hello, World!
some text, print more text on the same line
|
|
run result:
File “
print ‘Hello, World!’
^
SyntaxError: invalid syntax
Note:
以上通过 Python 2 使用 Printing "Hello, World"
是非常正常的,尽管如此,如果你有多个对象在小括号中,我们将创建一个元组,因为 print
在 Python 2 中是一个声明,而不是一个函数调用。
|
|
run result:
Python 2.7.7
(‘a’, ‘b’)
a b
整除
如果你正在移植代码,这个变化是特别危险的。或者你在 Python 2 上执行 Python 3 的代码。因为这个整除的变化表现在它会被忽视(即它不会抛出语法异常)。
因此,我还是倾向于使用一个 float(3)/2
或 3/2.0
代替在我的 Python 3 脚本保存在 Python 2 中的 3/2
的一些麻烦(并且反而过来也一样,我建议在你的 Python 2 脚本中使用 from __future__ import division
)
Python 2
|
|
run result:
Python 2.7.6
3 / 2 = 1
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0
Python 3
|
|
run result:
Python 3.4.1
3 / 2 = 1.5
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0
Unicode
Python 2 有 ASCII str() 类型,unicode()
是单独的,不是 byte
类型。
现在, 在 Python 3,我们最终有了 Unicode (utf-8)
字符串,以及一个字节类:byte
和 bytearrays
。
Python 2
|
|
run result:
Python 2.7.6
|
|
run result:
< type ‘unicode’ >
|
|
run result:
< type ‘str’ >
|
|
run result:
they are really the same
|
|
run result:
< type ‘bytearray’ >
Python 3
|
|
run result:
Python 3.4.1
strings are now utf-8 μnicoΔé!
|
|
run result:
Python 3.4.1 has < class ‘bytes’ >
|
|
run result:
and Python 3.4.1 also has < class ‘bytearray’>
|
|
run result:
-—————————————————————————————————————
TypeError Traceback (most recent call last)
< ipython-input-13-d3e8942ccf81> in < module>()
——> 1 ‘note that we cannot add a string’ + b’bytes for data’
TypeError: Can’t convert ‘bytes’ object to str implicitly
xrange
模块
在 Python 2 中 xrange()
创建迭代对象的用法是非常流行的。比如: for
循环或者是列表/集合/字典推导式。
这个表现十分像生成器(比如。“惰性求值”)。但是这个 xrange-iterable
是无穷的,意味着你可以无限遍历。
由于它的惰性求值,如果你不得仅仅不遍历它一次,xrange()
函数 比 range()
更快(比如 for
循环)。尽管如此,对比迭代一次,不建议你重复迭代多次,因为生成器每次都从头开始。
在 Python 3 中,range()
是像 xrange()
那样实现以至于一个专门的 xrange()
函数都不再存在(在 Python 3 中 xrange()
会抛出命名异常)。
|
|
Python 2
|
|
run result:
Python 2.7.6
timing range()
1000 loops, best of 3: 433 µs per loop
timing xrange()
1000 loops, best of 3: 350 µs per loop
Python 3
|
|
run result:
Python 3.4.1
timing range()
1000 loops, best of 3: 520 µs per loop
|
|
run result:
-—————————————————————————————————————
NameError Traceback (most recent call last)
——> 1 print(xrange(10))
NameError: name ‘xrange’ is not defined
Python3中的range
对象的__contains__
方法
另外一件值得一提的事情就是在 Python 3 中 range
有一个新的 __contains__
方法(感谢 Yuchen Ying 指出了这个),__contains__
方法可以加速 “查找” 在 Python 3.x 中显著的整数和布尔类型。
|
|
run result:
Python 3.4.1
1 loops, best of 3: 742 ms per loop
1000000 loops, best of 3: 1.19 µs per loop
基于以上的 timeit 的结果,当它使一个整数类型,而不是浮点类型的时候,你可以看到执行查找的速度是 60000 倍快。尽管如此,因为 Python 2.x 的 range
或者是 xrange
没有一个 __contains__
方法,这个整数类型或者是浮点类型的查询速度不会相差太大。
|
|
run result:
Python 2.7.7
1 loops, best of 3: 285 ms per loop
1 loops, best of 3: 179 ms per loop
1 loops, best of 3: 658 ms per loop
1 loops, best of 3: 556 ms per loop
下面说下 __contain__
方法并没有加入到 Python 2.x 中的证据:
|
|
run result:
Python 3.4.1
< slot wrapper ‘contains‘ of ‘range’ objects >
|
|
run result:
Python 2.7.7
-—————————————————————————————————————
AttributeError Traceback (most recent call last)
< ipython-input-7-05327350dafb> in < module>()
1 print ‘Python’, pythonversion()
——> 2 range.`_contains`
AttributeError: ‘builtinfunctionor_method’ object has no attribute `’__contains‘`
|
|
run result:
Python 2.7.7
-—————————————————————————————————————
AttributeError Traceback (most recent call last)
< ipython-input-8-7d1a71bfee8e> in < module>()
1 print ‘Python’, pythonversion()
——> 2 xrange.`_contains`
AttributeError: type object ‘xrange’ has no attribute '__contains__'
注意在 Python 2 和 Python 3 中速度的不同
有些人指出了 Python 3 的 range()
和 Python 2 的 xrange()
之间的速度不同。因为他们是用相同的方法实现的,因此期望相同的速度。尽管如此,这事实在于 Python 3 倾向于比 Python 2 运行的慢一点。
|
|
Python 3
|
|
run result:
Python 3.4.1
100 loops, best of 3: 2.68 ms per loop
Python 2
|
|
run result:
Python 2.7.6
1000 loops, best of 3: 1.72 ms per loop
Raising exceptions
Python 2 接受新旧两种语法标记,在 Python 3 中如果我不用小括号把异常参数括起来就会阻塞(并且反过来引发一个语法异常)。
Python 2
|
|
run result:
Python 2.7.6
|
|
run result:
-—————————————————————————————————————
IOError Traceback (most recent call last)
< ipython-input-8-25f049caebb0> in < module>()
——> 1 raise IOError, “file error”
IOError: file error
|
|
run result:
-—————————————————————————————————————
IOError Traceback (most recent call last)
< ipython-input-9-6f1c43f525b2> in < module>()
——> 1 raise IOError(“file error”)
IOError: file error
Python 3
|
|
run result:
Python 3.4.1
|
|
run result:
File “
raise IOError, “file error”
^
SyntaxError: invalid syntax
在 Python 3 中,可以这样抛出异常:
|
|
run result:
Python 3.4.1
-—————————————————————————————————————
OSError Traceback (most recent call last)
< ipython-input-11-c350544d15da> in < module>()
1 print(‘Python’, python_version())
——> 2 raise IOError(“file error”)
OSError: file error
Handling exceptions
在 Python 3 中处理异常也轻微的改变了,在 Python 3 中我们现在使用 as
作为关键词。
Python 2
|
|
run result:
Python 2.7.6
name ‘let_us_cause_a_NameError’ is not defined —> our error message
Python 3
|
|
run result:
Python 3.4.1
name ‘let_us_cause_a_NameError’ is not defined —> our error message
next()
函数 and .next()
方法
因为 next() (.next())
是一个如此普通的使用函数(方法),这里有另外一个语法改变(或者是实现上改变了),值得一提的是:在 Python 2.7.5 中函数和方法你都可以使用,next()
函数在 Python 3 中一直保留着(调用 .next()
抛出属性异常)。
Python 2
|
|
run result:
Python 2.7.6
‘b
Python 3
|
|
run result:
Python 3.4.1
‘a’
|
|
run result:
-—————————————————————————————————————
AttributeError Traceback (most recent call last)
< ipython-input-14-125f388bb61b> in < module>()
——> 1 my_generator.next()
AttributeError: ‘generator’ object has no attribute ‘next’
For
循环变量和全局命名空间泄漏
好消息:在 Python 3.x 中 for
循环变量不会再导致命名空间泄漏。
在 Python 3.x 中做了一个改变,在 What’s New In Python 3.0 中有如下描述:
“列表推导不再支持 [... for var in item1, item2, ...]
这样的语法。使用 [... for var in (item1, item2, ...)]
代替。也需要提醒的是列表推导有不同的语义: 他们关闭了在 list()
构造器中的生成器表达式的语法糖, 并且特别是循环控制变量不再泄漏进周围的作用范围域.”
Python 2
|
|
run result:
Python 2.7.6
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 4
Python 3
|
|
run result:
Python 3.4.1
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 1
比较不可排序类型
在 Python 3 中的另外一个变化就是当对不可排序类型做比较的时候,会抛出一个类型错误。
Python 2
|
|
run result:
Python 2.7.6
[1, 2] > ‘foo’ = False
(1, 2) > ‘foo’ = True
[1, 2] > (1, 2) = False
Python 3
|
|
run result:
Python 3.4.1
-—————————————————————————————————————
TypeError Traceback (most recent call last)
< ipython-input-16-a9031729f4a0> in < module>()
1 print(‘Python’, python_version())
——> 2 print(“[1, 2] > ‘foo’ = “, [1, 2] > ‘foo’)
3 print(“(1, 2) > ‘foo’ = “, (1, 2) > ‘foo’)
4 print(“[1, 2] > (1, 2) = “, [1, 2] > (1, 2))
TypeError: unorderable types: list() > str()
通过input()
解析用户的输入
幸运的是,在 Python 3 中已经解决了把用户的输入存储为一个 str
对象的问题。为了避免在 Python 2 中的读取非字符串类型的危险行为,我们不得不使用 raw_input()
代替。
Python 2
Python 2.7.6
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> my_input = input('enter a number: ')
enter a number: 123
>>> type(my_input)
<type 'int'>
>>> my_input = raw_input('enter a number: ')
enter a number: 123
>>> type(my_input)
<type 'str'>
Python 3
Python 3.4.1
[GCC 4.2.1 (Apple Inc. build 5577)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> my_input = input('enter a number: ')
enter a number: 123
>>> type(my_input)
<class 'str'>
返回可迭代对象,而不是列表
如果在 xrange 章节看到的,现在在 Python 3 中一些方法和函数返回迭代对象 — 代替 Python 2 中的列表
因为我们通常那些遍历只有一次,我认为这个改变对节约内存很有意义。尽管如此,它也是可能的,相对于生成器 —- 如需要遍历多次。它是不那么高效的。
而对于那些情况下,我们真正需要的是列表对象,我们可以通过 list()
函数简单的把迭代对象转换成一个列表。
Python 2
|
|
run result:
Python 2.7.6
[0, 1, 2]
< type ‘list’>
Python 3
|
|
run result:
Python 3.4.1
range(0, 3)
< class ‘range’>
[0, 1, 2]
在 Python 3 中一些经常使用到的不再返回列表的函数和方法:
zip()
map()
filter()
- dictionary’s
.keys()
method - dictionary’s
.values()
method - dictionary’s
.items()
method
更多的关于 Python 2 和 Python 3 的文章
下面是我建议后续的关于 Python 2 和 Python 3 的一些好文章。
移植到 Python 3
- Should I use Python 2 or Python 3 for my development activity?
- What’s New In Python 3.0
- Porting to Python 3
- Porting Python 2 Code to Python 3
- How keep Python 3 moving forward
Python 3 的拥护者和反对者