序列支持“+”和“*”运算,通常要求运算符两侧为相同的序列类型,并且运算的结果是生成一个新的序列而不会改变原来的任何一个运算对象。

>>> l=[1,2,3]
>>> l*5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5*'abcd'
'abcdabcdabcdabcdabcd'

有时,我们会需要用一定数量的嵌套列表来初始化一个列表。此时,最好的做法是采用list comprehension,如下

>>> board=[['_']*3 for i in range(3)]
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][1]='X'
>>> board
[['_', '_', '_'], ['_', 'X', '_'], ['_', '_', '_']]

与之相对的一种诱人却错误的方式是

>>> weird_board=[['_']*3]*3
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][1]='Y'
>>> weird_board
[['_', 'Y', '_'], ['_', 'Y', '_'], ['_', 'Y', '_']]

这段异常代码的问题在于,它的实际执行在本质上与下述代码类似:

>>> row=['_']*3
>>> board=[]
>>> for i in range(3):
...    board.append(row)

即,同一个row被引用了三次。不同的是,list comprehension的作用等同于下面的代码:

>>> board=[]
>>> for i in range(3):
...    row=['_']*3
...    board.append(row)
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][1]='Y'
>>> board
[['_', '_', '_'], ['_', 'Y', '_'], ['_', '_', '_']]

增量赋值“+=”,“*=”
增量赋值运算结果依赖于第一个运算对象,运算对象的类型不同,结果也有差别。
“+=”工作的基础是特殊方法__iadd__,如果该方法没有被实现,Python就会调用__add__。对于后者,由于运算中生成了新的对象,所以再赋值后会改变原操作对象的ID。该原理同样适用于“*=”,对应的特殊方法为__imul__。

>>> l=[1,2,3]
>>> id(l)
2297732364616
>>> l*=2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
2297732364616
>>> t=(1,2,3)
>>> id(t)
2297732591552
>>> t*=2
>>> t
(1, 2, 3, 1, 2, 3)
>>> id(t)
2297732664584

下面试着根据代码来判断哪个答案是正确的:

t=(1,2,[30,40])
t[2]+=[50,60]

a. t变为(1, 2, [30, 40, 50, 60])
b. TypeError: ‘tuple’ object does not support item assignment
c. a和b都不对
d. a和b都对

多数人会选择答案b,但实际上正确答案是d。以下是实际的执行结果。

>>> t=(1,2,[30,40])
>>> t[2]+=[50,60]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-19-a113d2524452> in <module>()
      1 t=(1,2,[30,40])
----> 2 t[2]+=[50,60]


TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

查看一下Python生成的字节码,能够更清楚的了解这是如何发生的。

>>> import dis
>>> dis.dis('S[a]+=b')
1           0 LOAD_NAME                0 (S)
              2 LOAD_NAME                1 (a)
              4 DUP_TOP_TWO
              6 BINARY_SUBSCR
              8 LOAD_NAME                2 (b)
             10 INPLACE_ADD
             12 ROT_THREE
             14 STORE_SUBSCR
             16 LOAD_CONST               0 (None)
             18 RETURN_VALUE
  • “BINARY_SUBSCR” 将S[a]的值置于栈顶(Top Of Stack,TOS)
  • “INPLACE_ADD” 执行增量运算TOS +=b,如果TOS是一个mutable对象,这一过程被成功执行。(上例中,它是一个列表)
  • “STORE_SUBSCR” 执行赋值,S[a]=TOS, 这一过程失败,因为S是immutable对象。(上例中,t是一个tuple)

虽然这个例子所展示的情况并不常见,但是从该例中还是能学到以下几点:

  • 将mutable项置于tuple中不是一个好主意。
  • 增量赋值并不是一个atomic operation,正如上例中,它会在完成部分工作后,才将错误抛出。
  • 检查Python的字节码并不困难,并且通常都会有很大帮助。