この例を見つけましたが、なぜ予期しない動作をするのか理解できませんか? [1, 8, 15]
または[2, 8, 22]
を出力する必要があると思いました。
array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
print(list(g))
>>>[8]
その理由は、作成時に、ジェネレーター_(a for b in c if d)
_はc
のみを評価するためです(これにより、時々はb
も予測可能になります)。ただし、a
、b
、d
は、消費時(各反復)に評価されます。ここでは、array
(array.count(x) > 0
)を評価するときに、囲んでいるスコープからのd
のcurrentバインディングを使用します。
たとえば、次のことができます。
_g = (x for x in [] if a)
_
事前にa
を宣言していません。ただし、ジェネレーターが消費されるときにa
が存在することを確認する必要があります。
しかし、同じようにすることはできません。
_g = (x for x in a if True)
_
要求に応じて:
共通のジェネレーター関数を使用して、類似した(ただし同一ではない)パターンを観察できます。
_def yielder():
for x in array:
if array.count(x) > 0:
yield x
array = [1, 8, 15]
y = yielder()
array = [2, 8, 22]
list(y)
# [2, 8, 22]
_
ジェネレーター関数は、消費前にその本体を実行しません。したがって、forループヘッダーのarray
でさえ遅れてバインドされます。さらに厄介な例は、反復中にarray
を「スイッチアウト」する場合です。
_array = [1, 8, 15]
y = yielder()
next(y)
# 1
array = [3, 7]
next(y) # still iterating [1, 8, 15], but evaluating condition on [3, 7]
# StopIteration raised
_
ジェネレータ式 のドキュメントから:
ジェネレータ式で使用される変数は、ジェネレータオブジェクトに対して
__next__()
メソッドが呼び出されると、遅延評価されます。 (通常のジェネレーターと同じ方法で)。ただし、左端のfor
句の反復可能な式はすぐに評価されるため、最初の値が取得されるポイントではなく、ジェネレータ式が定義されるポイントで発行されます。
だからあなたが走るとき
_array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
_
ジェネレータ式の最初のarray
のみが評価されます。 x
およびarray.count(x)
は、next(g)
を呼び出した場合にのみ評価されます。 array
が別のリストを指すようにするので_[2, 8, 22]
_ beforeジェネレーターを消費すると、「予期しない」結果が得られます。
_array = [2, 8, 22]
print(list(g)) # [8]
_
最初に配列を作成してその中の要素を割り当てると、配列の要素はあるメモリ位置を指し、ジェネレータはその位置(配列ではなく)を実行のために保持します。
ただし、配列の要素を変更すると変更されますが、両方に「8」が共通であるため、pythonは再割り当てせず、変更後に同じ要素を指します。
理解を深めるために、以下の例を見てください
array = [1, 8, 15]
for i in array:
print(id(i))
g = (x for x in array if array.count(x) > 0)
print('<======>')
array = [2, 8, 22]
for i in array:
print(id(i))
print(array)
print(list(g))
出力
140208067495680
140208067495904
140208067496128
<======>
140208067495712
140208067495904 # memory location is still same
140208067496352
[2, 8, 22]
[8]
実際、もっと注意深く見れば、それほどクレイジーではありません。見る
g = (x for x in array if array.count(x) > 0)
配列を調べ、既存の値の数がゼロより大きいかどうかを検索するジェネレーターを作成します。したがって、ジェネレーターは1
、8
、および15
のみを検索し、値を別の値に変更すると、ジェネレーターは新しい値ではなく、以前の値のみを検索します。 it(generator)は、配列にそれらがあったときに作成するためです。
したがって、配列に数千の値を入れると、それらの3つだけが検索されます。
混乱は、答えもそうですが、次の行にあります:g = (x for x in array if array.count(x) > 0)
この行を単純化すると、次のようになります。g = (x for x in array1 if array2.count(x) > 0)
これで、generatorが作成されると、array1
オブジェクトの参照が保持されます。したがって、array1
の値を他の値に変更する(つまり、新しい配列オブジェクトに設定する)場合でも、ジェネレーターのコピーには影響しません。 array1
の。 array1
のみがオブジェクト参照を変更しているため、ただし、array2
は動的にチェックされます。したがって、その値を変更すると、それが反映されます。
あなたはそれがバッターであることを理解するために次のコードから出力を見ることができます。それを見てください ここでオンラインで働いています :
array1 = [1, 8, 15] #Set value of `array1`
array2 = [2, 3, 4, 5, 8] #Set value of `array2`
print("Old `array1` object ID: " + repr(id(array1)))
print("Old `array2` object ID: " + repr(id(array2)))
g = (x for x in array1 if array2.count(x) > 0)
array1 = [0, 9] #Changed value of `array1`
array2 = [2, 8, 22, 1] #Changed value of `array2`
print("New `array1` object ID: " + repr(id(array1)))
print("New `array2` object ID: " + repr(id(array2)))
print(list(g))
出力:
Old `array1` object ID: 47770072262024
Old `array2` object ID: 47770072263816
New `array1` object ID: 47770072263944
New `array2` object ID: 47770072264008
[1, 8]