对序列使用+和*
序列是支持 + 和 * 操作的。通常 + 号两侧的序列由 相同类型的数据所构成,在拼接的过程中,两个被操作的序列都不会被 修改,Python 会新建一个包含同样类型数据的序列来作为拼接的结果。 如果想要把一个序列复制几份然后再拼接起来,更快捷的做法是把这个 序列乘以一个整数。同样,这个操作会产生一个新序列:
demo1
In [1]: l = [1,2,3,4,5] In [2]: l = [1,2,3,4,5] * 3 In [3]: l Out[3]: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5] In [4]: \'abc\' * 5 Out[4]: \'abcabcabcabcabc\'
+ 和 * 都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列。
如果在 a * n 这个语句中,序列 a 里的元素是对其他可变 对象的引用的话,你就需要格外注意了,因为这个式子的结果可能 会出乎意料。
比如,你想用 my_list = [[]] * 3 来初始化一个 由列表组成的列表,但是你得到的列表里包含的 3 个元素其实是 3 个引用,而且这 3 个引用指向的都是同一个列表。这可能不是你想要的效果。
demo2
In [16]: board = [[\'_\'] * 3 for i in range(3)] #1 In [17]: board Out[17]: [[\'_\', \'_\', \'_\'], [\'_\', \'_\', \'_\'], [\'_\', \'_\', \'_\']] In [18]: board[1][2] = \'x\' #2 In [19]: board Out[19]: [[\'_\', \'_\', \'_\'], [\'_\', \'_\', \'x\'], [\'_\', \'_\', \'_\']]
- 建立一个包含 3 个列表的列表,被包含的 3 个列表各自有 3 个元 素。打印出这个嵌套列表。
- 把第 1 行第 2 列的元素标记为 X,再打印出这个列表。
得到了我们期望的结果
下面看demo3的方法,看似捷径,却是错误的:
demo3
In [25]: weied_board = [[\'_\'] * 3] * 3 #1 In [26]: weied_board Out[26]: [[\'_\', \'_\', \'_\'], [\'_\', \'_\', \'_\'], [\'_\', \'_\', \'_\']] In [27]: weied_board[1][2] = \'x\' #2 In [28]: weied_board Out[28]: [[\'_\', \'_\', \'x\'], [\'_\', \'_\', \'x\'], [\'_\', \'_\', \'x\']]
- 外面的列表其实包含 3 个指向同一个列表的引用。当我们不做修改 的时候,看起来都还好。
- 一旦我们试图标记第 1 行第 2 列的元素,就立马暴露了列表内的 3 个引用指向同一个对象的事实。
含有 3 个指向同一对象的引用的列表是毫无用处的
demo3犯的错误本质上跟下面demo4的代码犯的错误一样:
row=[\'_\'] * 3 board = [] for i in range(3): board.append(row) #1
- 追加同一个行对象(row)3 次到游戏板(board)
相反,正确的写法如下:
In [1]: board = [] In [2]: for i in range(3): ...: row = [\'_\'] * 3 #1 ...: board.append(row) ...: In [3]: board Out[3]: [[\'_\', \'_\', \'_\'], [\'_\', \'_\', \'_\'], [\'_\', \'_\', \'_\']] In [4]: board[1][2] = \'x\' In [5]: board #2 Out[5]: [[\'_\', \'_\', \'_\'], [\'_\', \'_\', \'x\'], [\'_\', \'_\', \'_\']]
- 每次迭代中都新建了一个列表,作为新的一行(row)追加到游戏板 (board)。
- 正如我们所期待的,只有第 2 行的元素被修改。
这里涉及到引用可变对象的原理陷进,下次我们再说