python sequence中使用+和*

174 阅读5分钟

Using + 和 * with Sequence

fluent python 2nd edition

通常情况下,* 和 + 应用的Sequence需要时相同类型的,它们都不会背修改,而是返回一个相同类型的Sequence。

*号用于复制

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

*号和+号都会创建一个新对象,不会改变它们的操作对象。

Attention

当你所应用 * 号的Sequence 包含可变item时,你应该格外注意。例如my_list = [[]] * 3 会导致list中有三个指针相同的list。


下面将介绍使用*初始化list的缺陷

Building Lists of Lists

如下例,用列表推导式 来初始化有指定数量的嵌套list的list

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

这个例子与下面代码是等价的

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

列表推导式的等价代码如下

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

现在已经介绍了+和* 但还有+=和*=会产生不一样的结果,根据目标Sequence的可变还是不可变的不同而不同。

Augmented Assignment with Sequences

+=和*=的行为非常不同,这依赖于第一个被操作的对象。我们本章节就聚焦于+=,因为所介绍的特性也都适用于*=

让+=起作用的特殊方法是__iadd__

然而,如果__iadd__没有被实现,python就会调用__add__。对于a += b来说,如果a没实现__iadd__,但实现了__add__,也会实现a = a + b的效果,首先计算a + b得到一个新对象,然后将其赋值给a。 即 运算后的a的 对象id 可能会变化也可能不会,这取决于a是否实现了__iadd__

通常情况下,对于可变的Sequence,都会实现__iadd__。对于不可变对象,当然 不可能实现该方法。

下面是*=的演示,先是可变Sequence然后是不可变

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

A += Assignment Puzzler

本章节将介绍一个你需要注意的地方,考虑下面例子

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

结果会是怎样

A. t becomes (1, 2, [30, 40, 50, 60]). B. TypeError is raised with the message 'tuple' object does not support item assignment. C. Neither. D. Both A and B.

实际上 AB都会发生,Why,让我们看下python执行的字节码

>>> dis.dis('s[a] += b')
1 0 LOAD_NAME 0 (s)
3 LOAD_NAME 1 (a)
6 DUP_TOP_TWO
7 BINARY_SUBSCR
8 LOAD_NAME 2 (b)
11 INPLACE_ADD
12 ROT_THREE
13 STORE_SUBSCR
14 LOAD_CONST 0 (None)
17 RETURN_VALUE

将s[a]的值放在栈顶

执行栈顶 += b,这无疑是成功的

执行s[a]=栈顶。因为tuple是不可变的,所以会失败

通常情况下,* 和 + 应用的Sequence需要时相同类型的,它们都不会背修改,而是返回一个相同类型的Sequence。

*号用于复制

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

*号和+号都会创建一个新对象,不会改变它们的操作对象。

Attention

当你所应用 * 号的Sequence 包含可变item时,你应该格外注意。例如my_list = [[]] * 3 会导致list中有三个指针相同的list。


下面将介绍使用*初始化list的缺陷

Building Lists of Lists

如下例,用列表推导式 来初始化有指定数量的嵌套list的list

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

这个例子与下面代码是等价的

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

列表推导式的等价代码如下

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

现在已经介绍了+和* 但还有+=和*=会产生不一样的结果,根据目标Sequence的可变还是不可变的不同而不同。

Augmented Assignment with Sequences

+=和*=的行为非常不同,这依赖于第一个被操作的对象。我们本章节就聚焦于+=,因为所介绍的特性也都适用于*=

让+=起作用的特殊方法是__iadd__

然而,如果__iadd__没有被实现,python就会调用__add__。对于a += b来说,如果a没实现__iadd__,但实现了__add__,也会实现a = a + b的效果,首先计算a + b得到一个新对象,然后将其赋值给a。 即 运算后的a的 对象id 可能会变化也可能不会,这取决于a是否实现了__iadd__

通常情况下,对于可变的Sequence,都会实现__iadd__。对于不可变对象,当然 不可能实现该方法。

下面是*=的演示,先是可变Sequence然后是不可变

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

A += Assignment Puzzler

本章节将介绍一个你需要注意的地方,考虑下面例子

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

结果会是怎样

A. t becomes (1, 2, [30, 40, 50, 60]). B. TypeError is raised with the message 'tuple' object does not support item assignment. C. Neither. D. Both A and B.

实际上 AB都会发生,Why,让我们看下python执行的字节码

>>> dis.dis('s[a] += b')
1 0 LOAD_NAME 0 (s)
3 LOAD_NAME 1 (a)
6 DUP_TOP_TWO
7 BINARY_SUBSCR
8 LOAD_NAME 2 (b)
11 INPLACE_ADD
12 ROT_THREE
13 STORE_SUBSCR
14 LOAD_CONST 0 (None)
17 RETURN_VALUE

将s[a]的值放在栈顶

执行栈顶 += b,这无疑是成功的

执行s[a]=栈顶。因为tuple是不可变的,所以会失败