python散装笔记——149: 从Python 2迁移到Python 3的不兼容性(一)
与大多数语言不同,Python支持两个主要版本。自2008年Python 3发布以来,许多人已经完成了迁移,但仍有许多人没有。为了理解这两个版本,本节涵盖了Python 2和Python 3之间的重要差异。
1: 整数除法
标准的/符号在Python 3和Python 2中对整数的操作有所不同。
在Python 3中,整数除以另一个整数时,除法操作x / y表示真除法(使用__truediv__方法),并产生一个浮点结果。而在Python 2中,相同的操作表示经典除法,结果向下取整(也称为取地板值)。
例如:
Code | Python 2 输出 | Python 3 输出 |
3 / 2 | 1 | 1.5 |
2 / 3 | 0 | 0.6666666666666666 |
-3 / 2 | -2 | -1.5 |
Python 2.2中弃用了向零舍入的行为,但为了向后兼容,Python 2.7中仍然保留了它,并在Python 3中被移除。
注意:要在Python 2中获得浮点结果(而不是向下取整),我们可以在操作数中指定一个小数点。例如,2 / 3在Python 2中返回0,可以写成2 / 3.0、2.0 / 3或2.0 / 3.0以获得0.6666666666666666。
Code | Python 2 输出 | Python 3 输出 |
3.0 / 2.0 | 1.5 | 1.5 |
2 / 3.0 | 0.6666666666666666 | 0.6666666666666666 |
-3.0 / 2 | -1.5 | -1.5 |
还有地板除运算符(//),它在两个版本中的行为相同:它将结果向下舍入到最近的整数。(尽管使用浮点数时会返回一个浮点数)在两个版本中,//运算符都映射到__floordiv__。
Code | Python 2 输出 | Python 3 输出 |
3 // 2 | 1 | 1 |
2 // 3 | 0 | 0 |
-3 // 2 | -2 | -2 |
3.0 // 2.0 | 1.0 | 1.0 |
2.0 // 3 | 0.0 | 0.0 |
-3 // 2.0 | -2.0 | -2.0 |
可以使用operator模块中的原生函数显式强制执行真除法或浮点数除法:
from operator import truediv, floordiv
assert truediv(10, 8) == 1.25 # equivalent to `/` in Python 3
assert floordiv(10, 8) == 1 # equivalent to `//`
虽然清晰明确,但使用运算符函数进行每次除法可能会很繁琐。通常更倾向于改变/运算符的行为。一种常见的做法是通过在每个模块的开头添加from __future__ import division来消除典型除法行为:
# needs to be the first statement in a module
from __future__ import division
Code | Python 2 输出 | Python 3 输出 |
3 / 2 | 1.5 | 1.5 |
2 / 3 | 0.6666666666666666 | 0.6666666666666666 |
-3 / 2 | -1.5 | -1.5 |
from __future__ import division保证/运算符表示真除法,并且仅在包含__future__导入的模块内有效,因此没有理由不在所有新模块中启用它。
注意:一些其他编程语言使用向零舍入(截断),而不是像Python那样向下舍入到负无穷大(即在这些语言中-3 / 2 == -1)。这种行为可能会在移植或比较代码时造成混淆。
关于浮点运算符的注意事项:作为from __future__ import division的替代方法,可以使用普通的除法符号/,并确保至少有一个操作数是浮点数:3 / 2.0 == 1.5。然而,这可能被认为是一种不好的实践。很容易写出average = sum(items) / len(items)并忘记将其中一个参数转换为浮点数。此外,这种问题可能在测试期间经常被忽视,例如,如果你在测试中使用的是包含floats的数组,但在生产环境中收到的是ints数组。此外,如果相同的代码在Python 3中使用,期望3 / 2 == 1为True的程序将无法正确工作。
有关更改Python 3中的除法运算符以及为什么应该避免旧式除法的更详细理由,请参阅PEP 238。
有关除法的更多信息,请参阅简单数学主题。
2: 解包可迭代对象
Python 3.x版本 ≥ 3.0
在Python 3中,你可以解包一个可迭代对象,而无需知道其中的确切项目数量,甚至可以让一个变量保存可迭代对象的末尾。为此,你需要提供一个可以收集值列表的变量。这可以通过在名称前放置星号来完成。例如,解包一个列表:
first, second, *tail, last = [1, 2, 3, 4, 5]
print(first)
# Out: 1
print(second)
# Out: 2
print(tail)
# Out: [3, 4]
print(last)
# Out: 5
注意:使用*变量语法时,变量将始终是一个列表,即使原始类型不是列表也是如此。它可能根据原始列表中的元素数量包含零个或多个元素。
first, second, *tail, last = [1, 2, 3, 4]
print(tail)
# Out: [3]
first, second, *tail, last = [1, 2, 3]
print(tail)
# Out: []
print(last)
# Out: 3
类似地,解包一个str:
begin, *tail = "Hello"
print(begin)
# Out: 'H'
print(tail)
# Out: ['e', 'l', 'l', 'o']
解包日期的示例;在此示例中,_用作临时变量(我们只对年份值感兴趣):
person = ('John', 'Doe', (10, 16, 2016))
*_, (*_, year_of_birth) = person
print(year_of_birth)
# Out: 2016
值得一提的是,由于*会吸收可变数量的项目,因此你不能在同一个可迭代对象的赋值中使用两个*,因为它不知道有多少元素进入第一次解包,以及有多少进入第二次:
*head, *tail = [1, 2]
# Out: SyntaxError: two starred expressions in assignment
Python 3.x Version ≥ 3.5
到目前为止,我们已经讨论了在赋值中的解包。在Python 3.5中,*和**被扩展了。现在可以在一个表达式中进行多个解包操作:
{*range(4), 4, *(5, 6, 7)}
# Out: {0, 1, 2, 3, 4, 5, 6, 7}
Python 2.x Version ≥ 2.0
也可以将可迭代对象解包为函数参数:
iterable = [1, 2, 3, 4, 5]
print(iterable)
# Out: [1, 2, 3, 4, 5]
print(*iterable)
# Out: 1 2 3 4 5
Python 3.x Version ≥ 3.5
解包字典使用两个相邻的星号**(PEP 448):
tail = {'y': 2, 'z': 3}
{'x': 1, **tail}
# Out: {'x': 1, 'y': 2, 'z': 3}
这允许覆盖旧值和合并字典。
dict1 = {'x': 1, 'y': 1}
dict2 = {'y': 2, 'z': 3}
{**dict1, **dict2}
# Out: {'x': 1, 'y': 2, 'z': 3}
Python 3.x Version ≥ 3.0
Python 3移除了函数中的元组解包。因此,以下代码在Python 3中不起作用:
# Works in Python 2, but syntax error in Python 3:
map(lambda (x, y): x + y, zip(range(5), range(5)))
# Same is true for non-lambdas:
def example((x, y)):
pass
# Works in both Python 2 and Python 3:
map(lambda x: x[0] + x[1], zip(range(5), range(5)))
# And non-lambdas, too:
def working_example(x_y):
x, y = x_y
pass
有关详细理由,请参阅PEP 3113。
3: 字符串:字节与Unicode
Python 2.x Version ≤ 2.7
在Python 2中,存在两种字符串变体:由字节组成的类型(str)和由文本组成的类型(unicode)。
在Python 2中,str类型的对象始终是一个字节序列,但通常用于表示文本和二进制数据。
字符串字面量被解释为字节字符串。
s = 'Cafe' # type(s) == str
有两个例外:你可以通过在字面量前加上u来显式定义一个Unicode(文本)字面量:
s = u'Caf.' # type(s) == unicode
b = 'Lorem ipsum' # type(b) == str
或者,你可以指定整个模块的字符串字面量应创建Unicode(文本)字面量:
from __future__ import unicode_literals
s = 'Caf.' # type(s) == unicode
b = 'Lorem ipsum' # type(b) == unicode
为了检查变量是否为字符串(无论是Unicode还是字节字符串),可以使用:
isinstance(s, basestring)
Python 3.x Version ≥ 3.0
在Python 3中,str类型是一个Unicode文本类型。
s = 'Cafe' # type(s) == str
s = 'Caf.' # type(s) == str (note the accented trailing e)
此外,Python 3添加了一个bytes对象,适用于二进制“块”或写入编码无关的文件。要创建一个bytes对象,可以在字符串字面量前加上b,或者调用字符串的encode方法:
# Or, if you really need a byte string:
s = b'Cafe' # type(s) == bytes
s = 'Caf.'.encode() # type(s) == bytes
要测试一个值是否为字符串,可以使用:
isinstance(s, str)
Python 3.x Version ≥ 3.3
也可以在字符串字面量前加上u前缀,以方便Python 2和Python 3代码库之间的兼容性。由于在Python 3中,所有字符串默认都是Unicode,因此在字符串字面量前加上u没有效果:
u'Cafe' == 'Cafe'
但是,Python 2的原始Unicode字符串前缀ur不被支持:
>>> ur'Caf.'
File "", line 1
ur'Caf.'
^
SyntaxError: invalid syntax
注意,你必须将Python 3中的text (str)对象encode转换为其字节表示。此方法的默认编码是UTF-8。
你可以使用decode询问bytes对象它所代表的Unicode文本:
>>> b.decode()
'Caf.'
Python 2.x Version ≥ 2.6
虽然bytes类型在Python 2和Python 3中都存在,但unicode类型仅存在于Python 2中。要在Python 2中使用Python 3的隐式Unicode字符串,可以在代码文件的顶部添加以下内容:
from __future__ import unicode_literals
print(repr("hi"))
\# u'hi'
Python 3.x Version ≥ 3.0
另一个重要区别是,在Python 3中,对字节进行索引的结果是一个int输出,如下所示:
b"abc"[0] == 97
而以大小为1进行切片的结果是一个长度为1的字节对象:
b"abc"[0:1] == b"a"
此外,从Python 3.2开始,range也支持切片、索引和计数:
Python复制
print(range(1, 10)[3:7])
# 输出:range(3, 7)
print(range(1, 10).count(5))
# 输出:1
print(range(1, 10).index(7))
# 输出:6
使用特殊序列类型而不是列表的优点是,解释器不需要分配内存来创建列表并填充它:
Python 2.x版本 ≥ 2.3
# range(10000000000000000)
# 输出:
# Traceback (most recent call last):
# File "", line 1, in
# MemoryError
print(xrange(100000000000000000))
# 输出:xrange(100000000000000000)
由于后一种行为通常是期望的,因此在Python 3中移除了前一种行为。如果你仍然希望在Python 3中获得一个列表,你可以简单地使用list()构造函数对范围对象进行操作:
Python 3.x版本 ≥ 3.0
Python复制
print(list(range(1, 10)))
# 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
兼容性
为了在Python 2.x和Python 3.x版本之间保持兼容性,你可以使用外部包future中的builtins模块来实现向前兼容和向后兼容:
Python 2.x版本 ≥ 2.0
Python复制
# 向前兼容
from builtins import range
for i in range(10**8):
pass
Python 3.x版本 ≥ 3.0
Python复制
# 向后兼容
from past.builtins import xrange
for i in xrange(10**8):
pass
future库中的range支持在所有Python版本中进行切片、索引和计数,就像Python 3.2+中的内置方法一样。
4: print语句与print函数
在Python 2中,print是一个语句:
Python 2.x Version ≤ 2.7
print "Hello World"
print # print a newline
print "No newline", # add trailing comma to remove newline
print >>sys.stderr, "Error" # print to stderr
print("hello") # print "hello", since ("hello") == "hello"
print() # print an empty tuple "()"
print 1, 2, 3 # print space-separated arguments: "1 2 3"
print(1, 2, 3) # print tuple "(1, 2, 3)"
在Python 3中,print()是一个函数,具有用于常见用途的关键字参数:
Python 3.x Version ≥ 3.0
print "Hello World" # SyntaxError
print("Hello World")
print() # print a newline (must use parentheses)
print("No newline", end="") # end specifies what to append (defaults to newline)
print("Error", file=sys.stderr) # file specifies the output buffer
print("Comma", "separated", "output", sep=",") # sep specifies the separator
print("A", "B", "C", sep="") # null string for sep: prints as ABC
print("Flush this", flush=True) # flush the output buffer, added in Python 3.3
print(1, 2, 3) # print space-separated arguments: "1 2 3"
print((1, 2, 3)) # print tuple "(1, 2, 3)"
print函数具有以下参数:
print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
sep是分隔你传递给print的对象的内容。例如:
print('foo', 'bar', sep='~') # out: foo~bar
print('foo', 'bar', sep='.') # out: foo.bar
end是print语句末尾跟随的内容。例如:
print('foo', 'bar', end='!') # out: foo bar!
再次打印时,如果print语句以非换行符结尾,将打印在同一行:
print('foo', end='~')
print('bar')
\# out: foo~bar
注意:为了与未来版本兼容,print函数在Python 2.6及以后版本中也可用;但是,除非使用
from __future__ import print_function
禁用了print语句的解析,否则无法使用它。
有关理由,请参阅PEP 3105。
5: range和xrange函数之间的差异
在Python 2中,range函数返回一个列表,而xrange创建一个特殊的xrange对象,这是一个不可变序列,与其他内置序列类型不同,它不支持切片,也没有index或count方法:
Python 2.x Version ≥ 2.3
print(range(1, 10))
# Out: [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(isinstance(range(1, 10), list))
# Out: True
print(xrange(1, 10))
# Out: xrange(1, 10)
print(isinstance(xrange(1, 10), xrange))
# Out: True
在Python 3中,xrange被扩展为range序列,因此现在创建一个range对象。没有xrange类型:
Python 3.x Version ≥ 3.0
print(range(1, 10))
# Out: range(1, 10)
print(isinstance(range(1, 10), range))
# Out: True
# print(xrange(1, 10))
# The output will be:
# Traceback (most recent call last):
# File "", line 1, in
# NameError: name 'xrange' is not defined
此外,从Python 3.2开始,range也支持切片、索引和计数:
print(range(1, 10)[3:7])
# Out: range(3, 7)
print(range(1, 10).count(5))
# Out: 1
print(range(1, 10).index(7))
# Out: 6
使用特殊序列类型而不是列表的优点是,解释器不需要分配内存来创建列表并填充它:
Python 2.x Version ≥ 2.3
# range(10000000000000000)
# The output would be:
# Traceback (most recent call last):
# File "", line 1, in
# MemoryError
print(xrange(100000000000000000))
# Out: xrange(100000000000000000)
由于后一种行为通常是期望的,因此在Python 3中移除了前一种行为。如果你仍然希望在Python 3中获得一个列表,你可以简单地使用list()构造函数对range对象进行操作:
Python 3.x Version ≥ 3.0
print(list(range(1, 10)))
# Out: [1, 2, 3, 4, 5, 6, 7, 8, 9]
兼容性
为了在Python 2.x和Python 3.x版本之间保持兼容性,你可以使用外部包future中的builtins模块来实现向前兼容和向后兼容:
Python 2.x Version ≥ 2.0
#forward-compatible
from builtins import range
for i in range(10**8):
pass
Python 3.x Version ≥ 3.0
#backward-compatible
from past.builtins import xrange
for i in xrange(10**8):
pass
future库中的range在所有Python版本中都支持切片、索引和计数,就像Python 3.2+中的内置方法一样。