二次元や三次元でリストを使いたいときがあると思います。しかし、自分の意図しない動きをしたという経験がある方もいらっしゃるのではないかということでpythonのリストのお話をします。

In [1]:
ls = [0,1,2,3,4,5,6,7,8,9]
print(ls[3])
ls.append(10)
print(ls)
3
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

一次元のリストの場合はこんな感じでしょうか?まあ違和感はないですね。次は二次元のリストを扱ってみましょう。

In [2]:
ls = [[0,1,2],[3,4,5],[6,7,8]]
print(ls[2][1])
ls.append([10])
ls[3].append(11)
ls[3].append(12)
print(ls)
7
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [10, 11, 12]]

二次元のリストはこんな感じです。まあここまではそんなに困らないと思います。

In [3]:
ls = [0]*10
ls_2d = [[0]*5]*2
print('ls:',ls)
print('ls_2d',ls_2d)
ls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
ls_2d [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

こんな感じでリストを作成しました。ではここでそれぞれ値を更新してみましょう。

In [4]:
ls[3] = 1
ls_2d[0][3] = 1;

これでlsの四番目の要素を1に、ls_2dの最初の要素の四番目を1に変更できました。

出力してみます。

In [5]:
print(ls)
print(ls_2d)
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[[0, 0, 0, 1, 0], [0, 0, 0, 1, 0]]

おかしいことに気づいたでしょうか?lsは思い通りの結果を示しています。しかし、ls_2dは最初のリストの四番目と、二番目のリストの四番目が変更されています。これは、リストの中に入っているリストがすべて同じアドレスを参照しているからです。

実際にアドレスを確認してみます。

https://docs.python.org/ja/3/reference/datamodel.html
CPython では、id(x) は x が格納されているメモリ上のアドレスを返します。

In [6]:
print('一つ目の要素',id(ls_2d[0]))
print('二つ目の要素',id(ls_2d[1]))
一つ目の要素 2753480593096
二つ目の要素 2753480593096

おなじですね。これを改善するにはどうすればいいのでしょうか?
いきなり回答を言ってしまうと、リスト内包表記を使います。

In [7]:
ls_2d_improve = [[0]*5 for i in range(2)]
ls_2d_improve[0][3] = 1
print(ls_2d_improve)
[[0, 0, 0, 1, 0], [0, 0, 0, 0, 0]]

これでできました。念のためアドレスを確認しておくと、

In [8]:
print('一つ目の要素',id(ls_2d_improve[0]))
print('二つ目の要素',id(ls_2d_improve[1]))
一つ目の要素 2753497987464
二つ目の要素 2753497987336

違うアドレスになってますね。mutableだとかimmutableだとかそういう話はまたの機会に