记录博客 ZH-BLOG

Python 序列的 +、* 操作

时间:2018-07-30 23:19:13分类:python

序列的 + 和 *

+ 和 * 都是不修改原有的操作对象, 而是构建一个全新的序列。

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

如果一个序列当中包括一个可变的对象,这是使用 * 获取的是对象的引用。

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

使用 * 快速创建

>>> weird_board = [['_'] * 3] * 3
>>> weird_board # 列表中的三个列表其实引用的是同一个对象
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][2] = 'O'
>>> weird_board
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]

等效:

>>> t = [1,2,3]
>>> l = [5,6,t]
>>> ll = l * 3
>>> ll
[5, 6, [1, 2, 3], 5, 6, [1, 2, 3], 5, 6, [1, 2, 3]]
>>> t
[1, 2, 3]
>>> t[-1] = 10
>>> ll
[5, 6, [1, 2, 10], 5, 6, [1, 2, 10], 5, 6, [1, 2, 10]]

序列的增量赋值 += 和 *=

+= 背后的特殊方法是 __iadd__ (用于“就地加法”),就像调用了 a.extend(b) 一样。 

如果一个类没有实现这个方法的话, Python 会退一步调用 __add__ , 效果就变得跟 a = a + b 一样了,首先计算 a +b, 得到一个新的对象, 然后赋值给 a。 

也就是说,变量会不会被关联到新的对象上,完全取决于第一个操作对象有没有实现 __iadd__ 方法。

*= 背后的特殊方法是 __imul__ (用于“就地乘法”),与 += 类似。

>>> l = [1, 2, 3]
>>> id(l)
4311953800
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
4311953800
>>> t = (1, 2, 3)
>>> id(t)
4312681568
>>> t *= 2
>>> id(t)
4301348296

增量赋值不是原子操作

>>> t = (1,2,[30,40])
>>> t[2] = t[2] + [50,60]
Traceback (most recent call last):
  File "", line 1, in 
    t[2] = t[2] + [50,60]
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40])
>>> t[2] += [50,60]
Traceback (most recent call last):
  File "", line 1, in 
    t[2] += [50,60]
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
>>> t[2].extend([50,60])
>>> t
(1, 2, [30, 40, 50, 60, 50, 60])

1)第一种情况:运行异常,赋值失败。因为元组是不可变类型,t[2] 无法赋值。

2)第二种情况:运行异常,赋值成功。因为元组是不可变类型,所有运行异常,但 += 背后运行的是 __iadd__(t[2] 是列表,可变类型),可以完成自身“加”操作,但没法完成赋值操作,不能给 t[2] 赋值。

3)第三种情况:运行正常,赋值成功。因为 list 的 extend 方法是自身加操作,无赋值操作。没有对元组中 t[2] 进行赋值操作,所有运行无异常。