リストの初期化
問題
以下の処理を意図する。
- 2次元リストを準備し、そのリストに1列追加する
- 追加の際、各行を2行ずつに繰り返し、追加した列には全行を通したカウンター値を記録
1 2 3 4 |
Before: [['A', 'X'], ['B', 'Y'], ['C', 'Z']] After: [['A', 'X', 1], ['A', 'X', 2], ['B', 'Y', 3], ['B', 'Y', 4], ['C', 'Z', 5], ['C', 'Z', 6]] |
これを意図したコードで以下のように意図しない結果となった。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
source_list = [ ["A", "X"], ["B", "Y"], ["C", "Z"] ] print("Before:") print(source_list) new_list = [] counter = 1 for i in range(len(source_list)): new_row = source_list[i] new_row.append(counter) counter += 1 new_list.append(new_row) new_row = source_list[i] new_row.append(counter) counter += 1 new_list.append(new_row) print("After:") print(new_list) # Before: # [['A', 'X'], ['B', 'Y'], ['C', 'Z']] # After: # [['A', 'X', 1, 2], ['A', 'X', 1, 2], ['B', 'Y', 3, 4], ['B', 'Y', 3, 4], ['C', 'Z', 5, 6], ['C', 'Z', 5, 6]] |
原因
原因は14行目、19行目で複製すべきリストnew_row
を単純に代入しているためで、変数代入時に元のリストオブジェクトは複製されず重複して参照されるだけとなり、1つの変更が元のオブジェクトを通して全体に波及してしまう。
具体的にリストのi行目の処理を追うと以下の通り。
- 1回目の
new_row = source_list[i]
で元リストのi行目が1次元リストとして共有される - そのリストの最後尾にカウンター値が追加され、カウンターがインクリメントされる
- この時点で1次元リストの最後尾にカウンター値が追加され、
new_row
、source_list[i]
のいずれにも参照されている
- この時点で1次元リストの最後尾にカウンター値が追加され、
- 新しいリストに1次元リストが行として追加される
- この時点で、
source_list[i]
、new_row
、new_list[2*i]
が共通の1次元リストを参照している
- この時点で、
- 2回目の
new_row = source_list[i]
で元リストのi行目が共有される(実はこの処理は1番目の繰り返しであり意味がない) - そのリストの最後尾にカウンター値が追加され、カウンターがインクリメントされる
- この時点で、先に最後尾にカウンター値追加済みの1次元リストの最後尾に更にカウンター値が追加され、
new_row
、source_list[i]
のいずれにも参照されている
- この時点で、先に最後尾にカウンター値追加済みの1次元リストの最後尾に更にカウンター値が追加され、
- 新しいリストに1次元リストが行として追加される
- この時点で、
source_list[i]
、new_row
、new_list[2*i+1]
が共通の1次元リストを参照している
- この時点で、
この結果、source_list
も以下のように変更される。
1 |
[['A', 'X', 1, 2], ['B', 'Y', 3, 4], ['C', 'Z', 5, 6]] |
解決
この原因である重複参照を解消するため、元リスト各行の(参照を)代入するのではなく、copy()
メソッドで新しいインスタンスを生成することで、想定した結果を得る。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
source_list = [ ["A", "X"], ["B", "Y"], ["C", "Z"] ] print("Before:") print(source_list) new_list = [] counter = 1 for i in range(len(source_list)): new_row = source_list[i].copy() new_row.append(counter) counter += 1 new_list.append(new_row) new_row = source_list[i].copy() new_row.append(counter) counter += 1 new_list.append(new_row) print("After:") print(new_list) # Before: # [['A', 'X'], ['B', 'Y'], ['C', 'Z']] # After: # [['A', 'X', 1], ['A', 'X', 2], ['B', 'Y', 3], ['B', 'Y', 4], ['C', 'Z', 5], ['C', 'Z', 6]] |
結論
リストを代入するときに参照・複製を意識し、基本はcopy()
で複製。