對序列使用+和*
Python 程序員會默認序列是支持 + 和 * 操作的。通常 + 號兩側的序列由
相同類型的數據所構成,在拼接的過程中,兩個被操作的序列都不會被
修改,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'
+ 和 * 都遵循這個規律,不修改原有的操作對象,而是構建一個全新的
序列。
如果在 a * n 這個語句中,序列 a 里的元素是對其他可變
對象的引用的話,你就需要格外注意了,因為這個式子的結果可能
會出乎意料。比如,你想用 my_list = [[]] * 3 來初始化一個
由列表組成的列表,但是你得到的列表里包含的 3 個元素其實是 3
個引用,而且這 3 個引用指向的都是同一個列表。這可能不是你想
要的效果。
建立由列表組成的列表
有時我們會需要初始化一個嵌套著幾個列表的列表,譬如一個列表可能
需要用來存放不同的學生名單,或者是一個井字游戲板 上的一行方
塊。想要達成這些目的,最好的選擇是使用列表推導,見示例 2-12。
示例 2-12 一個包含 3 個列表的列表,嵌套的 3 個列表各自有 3 個
元素來代表井字游戲的一行方塊
>>> board = [['_'] * 3 for i in range(3)] ?
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X' ?
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
? 建立一個包含 3 個列表的列表,被包含的 3 個列表各自有 3 個元
素。打印出這個嵌套列表。
? 把第 1 行第 2 列的元素標記為 X,再打印出這個列表。
示例 2-13 展示了另一個方法,這個方法看上去是個誘人的捷徑,但實
際上它是錯的。
示例 2-13 含有 3 個指向同一對象的引用的列表是毫無用處的
>>> weird_board = [['_'] * 3] * 3 ?
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][2] = 'O' ?
>>> weird_board
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]
? 外面的列表其實包含 3 個指向同一個列表的引用。當我們不做修改
的時候,看起來都還好。
? 一旦我們試圖標記第 1 行第 2 列的元素,就立馬暴露了列表內的 3
個引用指向同一個對象的事實。
示例 2-13 犯的錯誤本質上跟下面的代碼犯的錯誤一樣:
row=['_'] * 3
board = []
for i in range(3):board.append(row) ?
? 追加同一個行對象(row)3 次到游戲板(board)。
相反,示例 2-12 中的方法等同于這樣做:
>>> board = []
>>> for i in range(3):
... row=['_'] * 3 # ?
... board.append(row)
...
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[2][0] = 'X'
>>> board # ?
[['_', '_', '_'], ['_', '_', '_'], ['X', '_', '_']]
? 每次迭代中都新建了一個列表,作為新的一行(row)追加到游戲板
(board)。
? 正如我們所期待的,只有第 2 行的元素被修改。
我們一直在說 + 和 *,但是別忘了我們還有 += 和 *=。隨著目標序列的
可變性的變化,這個兩個運算符的結果也大相徑庭。