サブリスト全体に予期せず反映されたリストの変更のリスト


645

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

myList = [[1] * 4] * 3

リストは次のようになりました。

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

次に、最も内側の値の1つを変更しました。

myList[0][0] = 5

今私のリストは次のようになります:

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

これは私が欲しかったことや期待したことではありません。誰かが何が起こっているのか、そしてそれをどのように回避するか説明できますか?

回答:


560

あなたが書くとき[x]*3、あなたは、本質的に、リストを得ます[x, x, x]。つまり、同じへの3つの参照を持つリストxです。次に、このシングルを変更すると、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つの方法は

[[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 = 21を2に変更することはできません。


24
誰もそれを指摘していないことに私は驚いています。ここでの答えは誤解を招くものです。[x]*3のように3つの参照を保存[x, x, x]するのxは、が変更可能な場合のみです。これは、たとえばa=[4]*3、以降a[0]=5a=[5,4,4].
機能しません

42
技術的には、まだ正しいです。[4]*3は本質的にと同等x = 4; [x, x, x]です。これは任意の原因としないこと、しかし、本当の問題は以来4不変です。また、他の例は実際には別のケースではありません。a = [x]*3; a[0] = 5x変更するのxではなく、変更するだけなので、変更可能であっても問題は発生しませんa。私の答えを誤解を招く、または不正確であるとは言いません。不変のオブジェクトを扱っている場合、足元で自分を撃つことはできません
CAdaker

19
@Allanqunziあなたは間違っています。実行x = 1000; lst = [x]*2; lst[0] is lst[1]-> True。Pythonはここでは、可変オブジェクトと不変オブジェクトを区別しません。
timgeb

129
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

フレームとオブジェクト

ライブPythonチューターVisualize


では、matrix = [[x] * 2]と書いても、例のように同じオブジェクトに対して2つの要素が作成されないのはなぜですか。同じ概念のようですが、何が欠けているのでしょうか。
Ahmed Mohamed 2017

@AhmedMohamed確かに、x参照するまったく同じオブジェクトの2つの要素を含むリストを作成します。を使用してグローバルに一意のオブジェクトをx = object()作成し 、次にmatrix = [[x] * 2]それらが真になる場合:matrix[0][0] is matrix[0][1]
nadrimajstor

@nadrimajstorでは、matrix [0]の変更が、2Dマトリックスを使用した上記の例のようにmatrix [1]に影響を与えないのはなぜですか。
Ahmed Mohamed 2017

(この例ではそれはあなたが変更可能なシーケンスの「コピー」を作る時にサプライズが来る@AhmedMohamed list場合そう)row = [x] * 2よりも、matrix = [row] * 2両方の行がまったく同じオブジェクトであり、今1行に変更した場合は、matrix[0][0] = y突然、他の1に反映(matrix[0][0] is matrix[1][0]) == True
nadrimajstor


52

実際、これはまさにあなたが期待することです。ここで何が起こっているのかを分解してみましょう:

あなたが書く

lst = [[1] * 4] * 3

これは次と同等です。

lst1 = [1]*4
lst = [lst1]*3

これlstは、すべてがを指す3つの要素を持つリストであることを意味しlst1ます。つまり、次の2行は同等です。

lst[0][0] = 5
lst1[0] = 5

ほかにlst[0]何もありませんlst1

望ましい動作を得るには、リスト内包表記を使用できます。

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

この場合、式はnごとに再評価され、異なるリストになります。


ここでの良い答えへのほんの少しの追加:あなたがしている場合id(lst[0][0])id(lst[1][0])またはさらにid(lst[0])、そして同じ場合に同じオブジェクトを扱っていることは明らかですid(lst[1])
セルギー・コロディアズニー

36
[[1] * 4] * 3

あるいは:

[[1, 1, 1, 1]] * 3

[1,1,1,1]内部リストの3つのコピーではなく、内部を3回参照するリストを作成するため、リストを(任意の位置で)変更するたびに、変更が3回表示されます。

次の例と同じです。

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

おそらく少し意外ではありません。


3
これを見つけるには、「is」演算子を使用できます。ls [0] is ls [1]はTrueを返します。
mipadi 2008年

9

問題を正しく説明した受け入れられた回答に加えて、リスト内包内で、使い捨て変数ではなく、xrange()より効率的なジェネレータを返すpython-2.x use を使用している場合(range()python 3では同じ仕事をします):_n

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

また、より多くのPython的な方法として、itertools.repeat()繰り返し要素の反復子オブジェクトを作成するために使用できます。

>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

PS numpyを使用して、1または0の配列のみを作成する場合np.onesnp.zerosおよび/または他の数値の使用に使用できますnp.repeat()

In [1]: import numpy as np

In [2]: 

In [2]: np.ones(4)
Out[2]: array([ 1.,  1.,  1.,  1.])

In [3]: np.ones((4, 2))
Out[3]: 
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.]])

In [4]: np.zeros((4, 2))
Out[4]: 
array([[ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.]])

In [5]: np.repeat([7], 10)
Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])

6

Pythonコンテナには、他のオブジェクトへの参照が含まれています。次の例をご覧ください。

>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

これbは、listへの参照である1つの項目を含むリストaです。リストaは変更可能です。

リストと整数の乗算は、リストをそれ自体に複数回追加することと同じです(一般的なシーケンス操作を参照)。したがって、例を続けます:

>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

cリストaに、と同等のリストへの2つの参照が含まれていることがわかりc = b * 2ます。

Python FAQにもこの動作の説明が含まれています:多次元リストを作成するにはどうすればよいですか?


6

myList = [[1]*4] * 3[1,1,1,1]メモリ内に1つのリストオブジェクトを作成し、その参照を3回コピーします。これはと同等obj = [1,1,1,1]; myList = [obj]*3です。への変更は、リストで参照されているobj場所の3つの場所に反映されobjます。正しい説明は次のとおりです。

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

または

myList = [[1 for __ in range(4)] for _ in range(3)]

ここ注意すべき重要なことは、*演算子はほとんどリテラルのリストを作成するために使用されるということです。1不変である、obj =[1]*4まだのリストが作成されます1フォームに渡る繰り返し4回[1,1,1,1]。ただし、不変オブジェクトへの参照が行われると、オブジェクトは新しいオブジェクトで上書きされます。

私たちがしなければ、この手段はobj[1]=42、その後objになるだろう[1,42,1,1] ではない [42,42,42,42]、いくつかの仮定することができるよう。これも確認できます。

>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440

>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440

2
リテラルについてではありません。obj[2] = 42 参照置き換えインデックスには2、そのインデックスによって参照されるオブジェクトを突然変異とは対照的に何である、myList[2][0] = ...(ないmyList[2]リストであり、assigmentの変更THAリストのインデックス0に参照)。もちろん、整数は変更可能ではありませんが、オブジェクトの種類がたくさんあります。また、[....]リスト表示表記もリテラル構文の形式であることに注意してください!複合オブジェクト(リストなど)とスカラーオブジェクト(整数など)を、可変オブジェクトと不変オブジェクトと混同しないでください。
Martijn Pieters

5

簡単に言えば、これはpythonではすべてが参照によって機能するために起こります。そのため、リストのリストを作成すると、基本的にそのような問題が発生します。

問題を解決するには、次のいずれかを実行します。1 . numpy.emptyの numpy配列ドキュメントを使用します。2 .リストに到達したら、リストを追加します。3.必要に応じて、辞書を使用することもできます


2

次のようにコードを書き直してみましょう。

x = 1
y = [x]
z = y * 4

myList = [z] * 3

次に、次のコードを実行して、すべてをより明確にします。コードが行うことは、基本的idに取得したオブジェクトのsを出力することです。

オブジェクトの「アイデンティティ」を返す

それらを特定し、何が起こるか分析するのに役立ちます:

print("myList:")
for i, subList in enumerate(myList):
    print("\t[{}]: {}".format(i, id(subList)))
    for j, elem in enumerate(subList):
        print("\t\t[{}]: {}".format(j, id(elem)))

そして、次の出力が得られます。

x: 1
y: [1]
z: [1, 1, 1, 1]
myList:
    [0]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [1]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [2]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528

それでは、一歩ずつ進めていきましょう。あなたはxどちらであり1、をy含む単一の要素リストを持っていxます。最初のステップは、基本的にであるy * 4新しいリストを取得することです。つまり、最初のオブジェクトへの参照である4つの要素を持つ新しいリストを作成します。正味のステップはかなり似ています。最初のステップと同じ理由で、基本的に、つまりとを返します。z[x, x, x, x]xz * 3[[x, x, x, x]] * 3[[x, x, x, x], [x, x, x, x], [x, x, x, x]]


2

誰もが何が起こっているのかを説明していると思います。私はそれを解決する一つの方法を提案します:

myList = [[1 for i in range(4)] for j in range(3)]

myList[0][0] = 5

print myList

そして、あなたは持っています:

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

2

より説明的に説明しようとすると、

操作1:

x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]

x[0][0] = 1
print(x) # [[1, 0], [0, 0]]

操作2:

y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [1, 0]]

最初のリストの最初の要素を変更しなかったのに、各リストの2番目の要素が変更されなかったのはなぜですか。これ[0] * 2は、実際には2つの数値のリストであり、0への参照は変更できないためです。

クローンコピーを作成する場合は、操作3を試してください。

import copy
y = [0] * 2   
print(y)   # [0, 0]

y = [y, copy.deepcopy(y)]  
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [0, 0]]

クローンコピーを作成するもう1つの興味深い方法、オペレーション4

import copy
y = [0] * 2
print(y) # [0, 0]

y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]

y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]

2

@spelchekr from Python list multiplication:[[...]] * 3は、変更時に互いにミラーリングする3つのリストを作成し、「なぜ外側の* 3だけが参照を作成し、内側のリストは作成しないのか」について同じ質問をしました?なぜすべて1でないのですか?」

li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]

ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

上記のコードを試した後の私の説明は次のとおりです。

  • 内部*3も参照を作成しますが、その参照はのよう[&0, &0, &0]に不変であり、次にli[0]変更する場合、const intの基礎となる参照を変更できない0ため、参照アドレスを新しい参照に変更でき&1ます。
  • while ma=[&li, &li, &li]limutableなので、を呼び出すとma[0][0]=1、ma [0] [0]はに等しくなるため&li[0]、すべての&liインスタンスは最初のアドレスをに変更します&1

1

組み込みのリスト関数を使用すると、次のようになります

a
out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#Displaying the list

a.remove(a[0])
out:[[1, 1, 1, 1], [1, 1, 1, 1]]
# Removed the first element of the list in which you want altered number

a.append([5,1,1,1])
out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
# append the element in the list but the appended element as you can see is appended in last but you want that in starting

a.reverse()
out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#So at last reverse the whole list to get the desired list

1
:あなたが第二段階にする場合は、第四の工程をドロップすることができますa.insert(0,[5,1,1,1])
U10-フォワード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.