python序列
Python程序员会默认序列是支持+和*操作的。通常+号的两侧的序列由相同类型的数据所构成,在拼接过程中,两个被操作的序列都不会被修改,python会新建一个包含同样类型数据的序列来作为拼接的结果。
如果想要把一个序列复制几分然后再拼接起来,更快捷的做法是把这个序列乘以一个整数。同样,这个操作会产生一个新序列:
l = [1, 2, 3]
print(l * 5)
# >>> [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
print(5 * 'abcd')
# >>> abcdabcdabcdabcdabcd
+和*都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列
注意:
如果在a*n这个语句中,序列a里的元素是对其他可变对象的引用的话,你就需要格外注意了,因为这个式子的结果可能会出乎意料。
比如,你想用my_list = [ [ ] ]*3,来初始化一个由列表组成的列表,但是你得到的列表里包含的3个元素其实是3个引用,而这三个引用都是指向同一个列表,这可能不是你要的结果。
接下来看看如何用来初始化一个由列表组成的列表*
建立有列表组成的列表
有时候我们会需要初始化一个嵌套着几个列表的列表,譬如一个列表可能需要用来存放不同的学生名单,或者是一个井子游戏板(又称过三关,是一种在3*3的方块矩阵上进行的游戏)上的一行方块。想要达成这些目的,最好的选择是使用列表推导式,见示例2-12:
# 建立一个包含3个列表的列表,被包含的3个列表各自有三个元素。打印出这个嵌套列表
board = [["_"] * 3 for i in range(3)]
print(board)
# >>> [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
# 把第一行第二列的元素标记为X,再打印出这个列表
board[1][2] = "x"
print(board)
# >>> [['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']]
接下来这个示例看上去是一个诱人的捷径,但实际上它是错的
含义3个指向同一对象的引用的列表是毫无用处的示例2-13
# 外面的列表其实包含3个指向同一个列表的引用。当我们不做修改的时候,看起来其实还好
weird_board = [['_'] * 3] * 3
print(weird_board)
# >>> [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
# 一旦我们试图标记第1行第2列的元素,就立马暴露了列表内的3个引用指向同一个对象的实事
weird_board[1][2] = "0"
print(weird_board)
# >>> [['_', '_', '0'], ['_', '_', '0'], ['_', '_', '0']]
这个位置犯得错误本质上跟下面的代码犯的错误一样。
row = ['_'] * 3
board = []
for i in range(3):
# 追加一个行对象(row)3次到游戏板(board)
board.append(row)
print(board)
# >>> [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
相反,示例2.12中的方法等同这样做:
board = []
for i in range(3):
# 每次迭代中都新建了一个列表,作为新的一行(row)追加到游戏板(board)
row = ['_'] * 3
board.append(row)
print(board)
# >>> [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
board[2][0] = "x"
# 正如我们所期待的,只有2行的元素被修改
print(board)
# >>> [['_', '_', '_'], ['_', '_', '_'], ['x', '_', '_']]
一直在说+和*,实际上我们还有+=和 *=。随着目标序列的可变性的变化,这两个运算符的结果也大径相庭。接下来,详细说说:
序列的增量赋值
增量赋值运算符+=和*=的表现取决于它们的第一个操作对象。简单起见,我们把讨论集中在增量加法(+=)上,但是这些概念对*=和其他运算符来说都是一样的。
+=背后的特殊方法是_iadd_(用于“就地加法”)。但是如果一个类没有实现这个方法的话,python会退一步调用_add_。考虑下面这个简单的表达式
>>> a += b
如果a实现了_iadd_方法,就会调用这个方法。同时对可变序列(例如list,bytearray和array.array)来说,a会就地改动,就像调用了a.extend(b)一样。但是如果a没有实现_iadd_的话,a += b 这个表达式的效果就变得和a = a+b 一样了:首先计算a+b,得到一个新的对象,然后赋值给a.也就是说,在这个表达式中,变量名会不会被关联到新的对象,完全取决于这个类型有没有实现_iadd_这个方法。
总体来说,可变序列一般都实现了_iadd_方法,因此+=就是就地加法,而不可变序列根本就不支持这个操作,对这个方法的实现也无从谈起。
上面所说的这些关于+=的概念也适用于*=,不同的是,后者相对应的是_imul_。关于_iadd_和_imul_之后我会单独写一篇讲解。