🍎

pythonで2次元配列を作る際の落とし穴

に公開2

問題の発端

Pythonでは、0で初期化した配列を作るとき、以下のように書けます。

l = [0] * 3
# [0, 0, 0]

この感覚で私は、空の配列を要素に持つ二次元配列を生成する際、次のように書いてしまいました。
通常の方法(リスト内包表記)と比較しても、一見するとちゃんと二次元配列ができているように見えますが、この書き方では問題が発生します。

l2_1 = [[]] * 3   # 今回私がやってしまった方法
l2_2 = [[] for _ in range(3)]   # 一般的な、リスト内包表記による方法

print(l2_1)  # [[], [], []]
print(l2_2)  # [[], [], []]

落とし穴の正体

id()関数により、オブジェクトidを確認すると、問題点が明らかになります。

for i in l2_1:
    print(id(i), end=" ")
print()
for i in l2_2:
    print(id(i), end=" ")
print()

# 90159031839936 90159031839936 90159031839936  (l2_1)
# 90159032860160 90159032859648 90159032859712  (l2_2)
  • l2_1 の各要素はすべて同じオブジェクトを参照している
  • l2_2 は要素ごとに異なるオブジェクトになっている

ことがわかるかと思います。

つまり l2_1 において一つの要素を操作すると、すべてに影響してしまいます。

l2_1[0].append(3)
print(l2_1)  # [[3], [3], [3]]

l2_2[0].append(3)
print(l2_2)  # [[3], [], []]

解決方法

空の配列で初期化された二次元配列を作るときはリスト内包表記を使いましょう。

l2 = [[] for _ in range(3)]
# [[], [], []]

さいごに

誤りがあればコメント等で指摘していただけると助かります。

Discussion

junerjuner

l2_1 = [[]] * 3
だと [] は1回だけ評価 (=インスタンスが使いまわされる)

l2_2 = [[] for _ in range(3)]
だと [] はループ回評価(=毎回インスタンスが作成される)

なんですね。