Python2和Python3的重要区别一览

相信很多python新手都有过这样的疑惑:到底应该使用哪个版本的python?它们之间的区别到底是怎么样的?

我的建议是首先从Python2.7.x或者3.x中选择一个作为入门,然后在实际做项目时根据需要再选择较为合适的版本即可。主要标准就是你选择的第三方库到底更兼容哪个版本。

但是,如果完全不了解这两个Python主要版本之间的区别的话,那么将会不可避免地踩到不少的“坑”。

 

1.print函数

print是我们经常会用到的函数,而且变化相当大,所以有值得分析一番的价值。

最大的改变就是在Python3中print是一个函数而不再是一个关键字,因此输出的对象必须要用括号括起来,不然会报语法错误。

Python2

#源代码
print 'Hello, World!'
print('Hello, World!')
print "text", ; print 'print more text on the same line'
#输出结果
Hello, World!
Hello, World!
text print more text on the same line

Python3

#源代码
print('Hello, World!')
print("some text,", end="")
print(' print more text on the same line')
#输出结果
Hello, World!
some text, print more text on the same line
#源代码
print 'Hello, World!'
#输出结果
SyntaxError: invalid syntax

当然原来的Python2中同样支持加括号的写法,如果输出对象只有一个,那么加不加这个括号其实没什么影响。但是,如果括号中有多个输出对象的话,那么括号的含义其实就是把它们组合成一个tuple,这有可能与我们的预期输出结果不一致。

Python2

#源代码
print('a', 'b')
print 'a', 'b'
#输出结果
('a', 'b')
a b

 

2.整数除法

Python2中如果是整数之间的除法,那么将会使用地板除(结果只取整数部分)得到的结果是一个整数,如果其中有一个数是浮点数则会使用精确除法,得到的结果是一个浮点数。

但是对于Python3来说所有的除法都是精确除法,得到的结果都是一个浮点数。如果想要使用地板除,则需要使用//符号。

Python2

#源代码
print '3 / 2 =', 3 / 2
print '3 // 2 =', 3 // 2
print '3 / 2.0 =', 3 / 2.0
print '3 // 2.0 =', 3 // 2.0
#输出结果
3 / 2 = 1
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

Python3

#源代码
print('3 / 2 =', 3 / 2)
print('3 // 2 =', 3 // 2)
print('3 / 2.0 =', 3 / 2.0)
print('3 // 2.0 =', 3 // 2.0)
#输出结果
3 / 2 = 1.5
3 // 2 = 1
3 / 2.0 = 1.5
3 // 2.0 = 1.0

 

3.字符串编码

Python2和Python3的字符串编码的差异十分明显,请仔细研究分析。

Python2

字符串类型

含义

具体的写法

str

某种编码(UTF-8,GBK等)类型的字符串

“在Python2中,单纯用引号括起来的字符,就是str。

unicode

Unicode类型的字符串

有两种写法:

①在该字符串的引号前加u前缀

②用unicode()强制转换

Python3

字符串类型

含义

具体的写法

bytes

某种编码(UTF-8,GBK等)类型的字节序列

普通字符串加上字母b作为前缀,就是表示bytes字符串了。

str

Unicode类型的字符串

Python 3.x中,直接输出的字符串(被单引号或双引号括起来的),就已经是Unicode类型的str了。

那么对于Python2的str和Python3的bytes,到底使用的是哪种编码格式呢?答案是:代码中字符串的默认编码与代码文件本身的编码一致。

下面以Python2版本为例讲解一个Python程序员常常遇到的坑————进行字符串格式转换时会遇到各种报错。

Python2

#! /usr/bin/env python 
# -*- coding: utf-8 -*- 
s = '中文'  # 注意这里的 str 是 str 类型的,而不是 unicode 
s.encode('gb18030')

因为s本身就是str类型的,因此Python会自动的先将s解码为unicode,然后再编码成gb18030

因为解码是python自动进行的,我们没有指明解码方式,python就会使用sys.defaultencoding指明的方式来解码。

很多情况下sys.defaultencoding是ANSCII,如果s不是这个类型就会出错。拿上面的情况来说,我的sys.defaultencodinganscii,而s的编码方式和文件的编码方式一致,是utf8的,所以出错了:UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe4 in position 0: ordinal not in range(128)

对于这种情况,我们有两种方法来改正错误:
一是明确的指示出s的编码方式

#! /usr/bin/env python 
# -*- coding: utf-8 -*- 

s = '中文' 
s.decode('utf-8').encode('gb18030')

二是更改sys.defaultencoding为文件的编码方式

#! /usr/bin/env python 
# -*- coding: utf-8 -*- 

import sys 
reload(sys) # Python2.5 初始化后会删除 sys.setdefaultencoding 这个方法,我们需要重新载入 
sys.setdefaultencoding('utf-8') 

str = '中文' 
str.encode('gb18030')

 

4.xrange

在Python2.x中,xrange()函数用于得到一个生成器(延迟计算)从而避免使用range()来生成整个list,导致不必要的内存占用。同时所有的无限序列只能通过xrange来获得。

而在Python3中,range()函数实际上等同于Python2中的xrange(),同时为了避免重复,取消了xrange()这个函数。

如果要想使用range()获得一个list,必须显式调用:
list(range(10))

还有一个十分隐蔽的区别,Python3的range由于新增了一个__contains__方法,因此对于整数和布尔型变量的in操作将会大大地加快。

Python3

#源代码
x = 10000000

def val_in_range(x, val):
    return val in range(x)

%timeit val_in_range(x, x/2)
%timeit val_in_range(x, x//2)
#输出结果
1 loops, best of 3: 742 ms per loop
1000000 loops, best of 3: 1.19 µs per loop

注意因为在Python3中默认是精确除法,因此x/2得到的是一个浮点数。range对于浮点数的in操作可没有进行特殊的优化。

Python2

#源代码
x = 10000000

def val_in_range(x, val):
    return val in range(x)

def val_in_xrange(x, val):
    return val in xrange(x)

%timeit val_in_xrange(x, x/2.0)
%timeit val_in_xrange(x, x/2)
%timeit val_in_range(x, x/2.0)
%timeit val_in_range(x, x/2)
#输出结果
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

显然,Python2对于整型和浮点数的in操作所耗费的时间没有质的区别。原因就是Python2的range和xrange都没有__contains__方法。

 

5.速度差异

有些同学可能看过一下这么一段测试代码:

#源代码
import timeit

n = 10000
def test_range(n):
    for i in range(n):
        pass

def test_xrange(n):
    for i in xrange(n):
        pass

Python2

#源代码
print '\ntiming range()'
%timeit test_range(n)

print '\n\ntiming xrange()'
%timeit test_xrange(n)
#输出结果
timing range()
1000 loops, best of 3: 433 µs per loop


timing xrange()
1000 loops, best of 3: 350 µs per loop

对于Python2来说,xrange确实要比range要快。但是如果你要反复迭代同一个序列,那么有可能使用range会更合适,因为它能保证所有元素的生成只计算一次,而xrange每次迭代都要重新计算。

Python3

#源代码
print('\ntiming range()')
%timeit test_range(n)
#输出结果
timing range()
1000 loops, best of 3: 520 µs per loop

既然上文都已经提到过,Python3的range其实相当于Python2的xrange,那它们之间为什么会表现出如此大的性能差别呢?同样的实现机制,Python2的xrange只需要350µs,但是Python3的range却需要520µs。

其实Python3的运行效率在不少情况下比不上Python2是一个我们不得不承认的事实。比如说,同样是运行下面的代码:

#源代码
def test_while():
    i = 0
    while i < 20000:
        i += 1
    return

print('Python', python_version())
%timeit test_while()
#输出结果
Python 3.4.1
100 loops, best of 3: 2.68 ms per loop
#输出结果
Python 2.7.6
1000 loops, best of 3: 1.72 ms per loop

Python3就是比Python2要慢。。

 

6.抛出异常

抛出异常使用的是raise关键字,Python2支持加括号和不加括号两种写法,但是Python3对于不加括号的写法会报语法错误。

#源代码
raise IOError, "file error"
#源代码
raise IOError("file error")

 

7.异常处理

Python3中的捕获和处理异常,声明异常变量时需要使用as关键字。

Python2

#源代码
try:
    let_us_cause_a_NameError
except NameError, err:
    print err, '--> our error message'

Python3

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

 

8.next()函数和.next()方法

在Python2中,我们可以对一个可迭代对象使用next()函数或者.next()方法,它们两者的作用一致。但是到了Python3,就只能使用next()函数了。(调用.next()方法会AttributeError报错)

Python2

#源代码
my_generator = (letter for letter in 'abcdefg')
print(next(my_generator))
print(my_generator.next())
#输出结果
a
b

Python3

#源代码
my_generator = (letter for letter in 'abcdefg')
print(next(my_generator))
print(my_generator.next())
#输出结果
a
AttributeError: 'generator' object has no attribute 'next'

 

9.for循环变量污染全局命名空间的问题

Python3中的for循环变量将不会再污染全局命名空间。

Python2

#源代码
i = 1
print 'before: i =', i
print 'comprehension: ', [i for i in range(5)]
print 'after: i =', i
#输出结果
before: i = 1
comprehension:  [0, 1, 2, 3, 4]
after: i = 4

Python3

#源代码
i = 1
print('before: i =', i)
print('comprehension:', [i for i in range(5)])
print('after: i =', i)
#输出结果
before: i = 1
comprehension: [0, 1, 2, 3, 4]
after: i = 1

 

10.比较运算的规范性

在Python2中,如果强行比较两个相差较大(不能直接比较)的对象时,会返回一个无法预测的布尔值。而到了Python3,这将会直接TypeError报错。

Python2

#源代码
print "[1, 2] > 'foo' = ", [1, 2] > 'foo'
print "(1, 2) > 'foo' = ", (1, 2) > 'foo'
print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2)
#输出结果
[1, 2] > 'foo' =  False
(1, 2) > 'foo' =  True
[1, 2] > (1, 2) =  False

Python3

#源代码
print "[1, 2] > 'foo' = ", [1, 2] > 'foo'
print "(1, 2) > 'foo' = ", (1, 2) > 'foo'
print "[1, 2] > (1, 2) = ", [1, 2] > (1, 2)
#输出结果
TypeError: '>' not supported between instances of 'list' and 'str'
TypeError: '>' not supported between instances of 'tuple' and 'str'
TypeError: '>' not supported between instances of 'list' and 'tuple'

 

11.input()函数

Python2中的input()函数使用起来十分不方便,当我们输入字符串或者字符的时候, 要用双引号或者单引号包起来不然就会报错。

而且返回的变量类型并不一定总是string,根据输入内容的不同有可能是其他类型。
这种不确定性就给后续编程带来极大的不便,这时我们需要使用raw_input()来保证得到的一定是一个string。

到了Python3,input()函数实际上相当于Python2的raw_input()。跟xrange和range类似,为了避免重复,Python3取消了raw_input()这个函数。

Python2

>>> 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'>

Python3

>>> my_input = input('enter a number: ')
enter a number: 123
>>> type(my_input)
<class 'str'>

 

12.善于使用__future__模块

Python提供了一个叫__future__的模块,可以把下一个新版本的特性导入到当前版本。
如果你想在在2.7版本的代码中,使用Python 3.x的新的字符串的表示方法,可以通过加载__future__模块中的unicode_literals来达到这一目的:

Python2

#源代码
from __future__ import unicode_literals

print '\'xxx\' is unicode?', isinstance('xxx', unicode)
print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode)
print '\'xxx\' is str?', isinstance('xxx', str)
print 'b\'xxx\' is str?', isinstance(b'xxx', str)
#输出结果
'xxx' is unicode? True
u'xxx' is unicode? True
'xxx' is str? False
b'xxx' is str? True

同样地,如果你想在Python2.7的代码中直接使用Python 3.x的除法,可以通过__future__模块的division实现。

Python2

#源代码
from __future__ import division

print '10 / 3 =', 10 / 3
print '10.0 / 3 =', 10.0 / 3
print '10 // 3 =', 10 // 3
#输出结果
10 / 3 = 3.33333333333
10.0 / 3 = 3.33333333333
10 // 3 = 3