web-dev-qa-db-ja.com

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

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]]  

これは私が望んだものでも期待したものでもありません。誰かが何が起こっているのか、それを回避する方法を説明してもらえますか?

541

[x]*3と書くと、本質的にリスト[x, x, x]が得られます。つまり、同じxへの3つの参照を持つリスト。その後、この単一のxを変更すると、3つのすべての参照を介して表示されます。

修正するには、各位置に新しいリストを作成する必要があります。それを行う1つの方法は

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

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


なぜ*がリスト内包表記のように独立したオブジェクトを作成できないのか疑問に思うかもしれません。これは、乗算演算子*が式を見ずにオブジェクトを操作するためです。 *を使用して[[1] * 4]に3を掛けると、*[[1] * 4]が評価される1要素のリストのみを参照し、[[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のようなことをして、1を2に変えることはできません。

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

Frames and Objects

ライブPythonチューターの視覚化

116
nadrimajstor

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

あなたが書く

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 xrange(3) ]

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

45
PierreBdR
[[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]]

おそらくそれほど驚くことではありません。

32
Blair Conrad

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

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

また、はるかにPythonicの方法として itertools.repeat() を使用して、繰り返される要素の反復子オブジェクトを作成できます。

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

追伸numpyを使用して、1または0の配列のみを作成する場合は、np.onesおよびnp.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
Kasrâmvd

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

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

4

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

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

このbは、リストaへの参照である1つのアイテムを含むリストです。リストaは変更可能です。

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

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

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

Python FAQには、この動作の説明も含まれています。 多次元リストの作成方法

3
Zbyněk Winkler

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

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

または

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

ここで注意すべき重要なことは、*演算子がmostlyを作成するために使用されることですのリストリテラル1はリテラルであるため、obj =[1]*4[1,1,1,1]を作成します。各1はアトミックであり、not1の参照が繰り返されます4回。つまり、obj[2]=42を実行すると、obj[1,1,42,1]になりますnot [42,42,42,42] 一部の人が想定するように。

2
jerrymouse

もっと説明的に説明しようとすると、

操作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]]

クローンコピーを作成する別の興味深い方法、操作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]]
1
Adil Abbasi

次の方法でコードを書き換えましょう。

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

それでは、ステップバイステップで行ってみましょう。 1であるxと、yを含む単一の要素リストxがあります。最初のステップはy * 4で、これは新しいリストzを取得します。これは基本的に[x, x, x, x]です。つまり、最初のxオブジェクトへの参照である4つの要素を持つ新しいリストを作成します。ネットのステップはかなり似ています。最初のステップと同じ理由で、基本的にz * 3を実行します。これは[[x, x, x, x]] * 3であり、[[x, x, x, x], [x, x, x, x], [x, x, x, x]]を返します。

1
bagrat

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

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]]
1
awulll

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

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
0
anand tripathi

Pythonリストの乗算:[[...]] * 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に変更できます。
  • ma=[&li, &li, &li]liは可変であるため、ma[0][0]=1を呼び出すと、ma [0] [0]は&li[0]と等しくなるため、すべての&liインスタンスはその最初のアドレスを&1に変更します。
0
ouxiaogu