1. ホーム
  2. python

[解決済み] リストの変更が不意にサブリスト全体に反映されたリスト

2022-03-20 09:06:15

質問

Pythonでリストを作成する必要があったので、次のように入力しました。

my_list = [[1] * 4] * 3

リストはこんな感じでした。

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  

そして、一番内側の値の一つを変更しました。

my_list[0][0] = 5

これで、私のリストは次のようになります。

[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  

というのは、私が望んでいたものでも、期待していたものでもありません。どなたか、何が起こっているのか、どうすれば回避できるのか、説明していただけませんか?

解決方法は?

と書くと [x]*3 というリストが得られます。 [x, x, x] . つまり、3つの参照があるリストで、同じ x . この1つの x は、その3つの参照先すべてから見えるようになります。

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}\n"
    f"id(l[1]): {id(l[1])}\n"
    f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

これを解決するには、各ポジションで新しいリストを作成するようにする必要があります。その方法のひとつは

[[1]*4 for _ in range(3)]

を再評価します。 [1]*4 の代わりに、毎回評価し、1つのリストに対して3つの参照を行います。


と疑問に思うかもしれませんが * は、リスト内包のように独立したオブジェクトを作ることができません。それは、乗算演算子 * は、式を見ることなく、オブジェクトに対して操作します。式は見えません。 * を掛け合わせると [[1] * 4] を3で割ったものです。 * は 1 要素のリスト [[1] * 4] は評価されません。 [[1] * 4 の式テキストを表示します。 * は、その要素のコピーを作成する方法も、再評価する方法も知らない。 [[1] * 4] そして、一般的には、要素をコピーする方法さえないかもしれません。

唯一の選択肢 * は、新しいサブリストを作ろうとするのではなく、既存のサブリストへの新しい参照を作ることです。それ以外の方法は、一貫性がないか、根本的な言語設計の決定から大きく再設計する必要があります。

これに対して、リスト内包は、繰り返しごとに要素式を再評価する。 [[1] * 4 for n in range(3)] は再評価されます。 [1] * 4 毎回同じ理由で [x**2 for x in range(3)] 再評価 x**2 を毎回使用します。の評価は毎回 [1] * 4 は新しいリストを生成するので、リスト内包はあなたが望んだとおりの働きをします。

ちなみに [1] * 4 の要素もコピーされません。 [1] しかし、整数は不変なので、それは問題ではありません。のようなことはできません。 1.value = 2 を2にすることができます。