サブリスト全体に予期せずに反映されたリストの変更のリスト
-
04-07-2019 - |
質問
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]]
これは私が望んだものでも期待したものでもありません。誰かが何が起こっているのか、それを回避する方法を説明してもらえますか?
解決
[x] * 3
と書くと、本質的にリスト [x、x、x]
が得られます。つまり、同じ x
への3つの参照を含むリスト。その後、この単一の x
を変更すると、3つすべての参照を介して表示されます。
修正するには、各位置に新しいリストを作成する必要があります。その方法の1つは
[[1]*4 for _ in range(3)]
一度評価して1つのリストを3回参照する代わりに、毎回 [1] * 4
を再評価します。
*
がリスト内包表記のように独立したオブジェクトを作成できないのはなぜかと思うかもしれません。乗算演算子 *
は、式を見ずにオブジェクトを操作するためです。 *
を使用して [[1] * 4]
に3を掛けると、 *
は1要素リスト [[ 1] * 4]
は、 [[1] * 4
式テキストではなく、評価されます。 *
には、その要素のコピーを作成する方法、 [[1] * 4]
を再評価する方法、およびコピーが必要かどうかもわかりません。 、要素をコピーする方法さえないかもしれません。
*
が持つ唯一のオプションは、新しいサブリストを作成しようとする代わりに、既存のサブリストへの新しい参照を作成することです。それ以外は一貫性がないか、基本的な言語設計の決定を大幅に再設計する必要があります。
対照的に、リスト内包表記は、反復ごとに要素式を再評価します。 [[1] * 4 for range(3)]
は同じ理由で毎回 [1] * 4
を再評価します [x ** 2 for x in range(3)]
は毎回 x ** 2
を再評価します。 [1] * 4
を評価するたびに新しいリストが生成されるため、リストの内包表記は必要なことを行います。
ちなみに、 [1] * 4
は [1]
の要素もコピーしませんが、整数は不変なので重要ではありません。 1.value = 2
のようなことをして、1を2に変えることはできません。
他のヒント
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
実際、これはまさにあなたが期待するものです。ここで何が起こっているのか分解しましょう:
書きます
lst = [[1] * 4] * 3
これは次と同等です:
lst1 = [1]*4
lst = [lst1]*3
これは、 lst
が3つの要素がすべて lst1
を指すリストであることを意味します。つまり、次の2行は同等です。
lst[0][0] = 5
lst1[0] = 5
As lst [0]
は lst1
に他なりません。
目的の動作を取得するには、リスト内包表記を使用できます:
lst = [ [1]*4 for n in xrange(3) ]
この場合、式はnごとに再評価され、異なるリストになります。
[[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]]
おそらく、それほど驚くことではありません。
問題を正しく説明した受け入れられた答えに加えて、リストの理解の範囲内で、python-2.xを使用している場合は、より効率的なジェネレーターを返す xrange()
( python 3のrange()
は同じ仕事をします)スローアウェイ変数 n
の代わりに _
:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
また、 itertools.repeat()
を使用して、繰り返される要素の反復子オブジェクトを作成します。
>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]
PS 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])
Pythonではすべてが参照で機能するため、簡単な言葉でこれが起こっています。したがって、リストのリストをそのように作成すると、基本的にこのような問題が発生します。
問題を解決するには、次のいずれかを実行できます。 1. numpy配列 numpy.emptyのドキュメント 2.リストに到達したら、リストを追加します。 3.必要に応じて辞書を使用することもできます
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には、この動作の説明も含まれています。操作方法多次元リストを作成しますか?
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)]
重要な注意事項は、リテラルのリストを作成するために *
演算子がほとんど使用されることです。 1
はリテラルなので、 obj = [1] * 4
は [1,1,1,1]
を作成し、各 > 1
はアトミックであり、 1
の参照ではなく4回繰り返されたではありません。つまり、 obj [2] = 42
を実行すると、 obj
は [1,1,42,1]
not 一部の人が想定しているように。 [42,42,42,42]
次の方法でコードを書き換えましょう:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
その後、次のコードを実行してすべてを明確にします。コードは基本的に id
“ ID”を返しますオブジェクトの
そしてそれらを特定し、何が起こるかを分析するのに役立ちます:
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
と、 x
を含む単一の要素リスト y
があります。最初のステップは 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つの方法を提案します:
myList = [[range(4)のiの1]] [range(3)のjの]]
myList[0][0] = 5
myListを印刷
そして次のことができます:
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
より説明的に説明しようとしています
操作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]]
組み込みのリスト関数を使用すると、次のように実行できます
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