web-dev-qa-db-ja.com

ジェネレータ式は、ジェネレータの作成後に割り当てられたリストを使用します

この例を見つけましたが、なぜ予期しない動作をするのか理解できませんか? [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]
19
Gvyntyk

その理由は、作成時に、ジェネレーター_(a for b in c if d)_はcのみを評価するためです(これにより、時々bも予測可能になります)。ただし、abdは、消費時(各反復)に評価されます。ここでは、arrayarray.count(x) > 0)を評価するときに、囲んでいるスコープからのdcurrentバインディングを使用します。

たとえば、次のことができます。

_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
_
18
schwobaseggl

ジェネレータ式 のドキュメントから:

ジェネレータ式で使用される変数は、ジェネレータオブジェクトに対して __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]
_
6
Eugene Yarmash

最初に配列を作成してその中の要素を割り当てると、配列の要素はあるメモリ位置を指し、ジェネレータはその位置(配列ではなく)を実行のために保持します。

ただし、配列の要素を変更すると変更されますが、両方に「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]
1
ansu5555

実際、もっと注意深く見れば、それほどクレイジーではありません。見る

g = (x for x in array if array.count(x) > 0)

配列を調べ、既存の値の数がゼロより大きいかどうかを検索するジェネレーターを作成します。したがって、ジェネレーターは18、および15のみを検索し、値を別の値に変更すると、ジェネレーターは新しい値ではなく、以前の値のみを検索します。 it(generator)は、配列にそれらがあったときに作成するためです。

したがって、配列に数千の値を入れると、それらの3つだけが検索されます。

0

混乱は、答えもそうですが、次の行にあります: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]
0
cse