web-dev-qa-db-fra.com

L'expression du générateur utilise la liste attribuée après la création du générateur

J'ai trouvé cet exemple et je ne comprends pas pourquoi cela fonctionne de manière imprévisible? J'ai supposé qu'il devait générer [1, 8, 15] ou [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]
16
Gvyntyk

En effet, au moment de la création, le générateur (a for b in c if d) n'évalue que c (ce qui parfois rend également b prévisible). Mais a, b, d sont évalués au moment de la consommation (à chaque itération). Ici, il utilise la liaison current de array à partir de la portée englobante lors de l'évaluation de d (array.count(x) > 0).

Vous pouvez par exemple faire:

g = (x for x in [] if a)

Sans avoir déclaré a à l'avance. Cependant, vous devez vous assurer que a existe lorsque le générateur est utilisé.

Mais vous ne pouvez pas faire la même chose:

g = (x for x in a if True)

À la demande:

Vous pouvez observer des modèles similaires (mais non identiques) avec une fonction de générateur commune:

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]

La fonction de générateur n'exécute aucun de ses corps avant la consommation. Par conséquent, même la variable array dans l'en-tête de la boucle for est liée tardivement. Un exemple encore plus troublant se produit lorsque nous "commutons" array pendant l'itération:

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
17
schwobaseggl

À partir de la documentation sur expressions du générateur :

Les variables utilisées dans l'expression du générateur sont évalué paresseusement quand la méthode __next__() est appelée pour l'objet générateur (de la même manière que les générateurs normaux). Cependant, l'expression itérable dans le fichier leftmost for clause est évalué immédiatement, de sorte qu'une erreur produite par elle sera émise au point où le générateur l'expression est définie plutôt que là où se trouve la première valeur est récupéré.

Alors quand tu cours

array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)

seule la première array de l'expression du générateur est évaluée. x et array.count(x) ne seront évalués que lorsque vous appelez next(g). Puisque vous faites array pointer vers une autre liste [2, 8, 22]avant consommant le générateur, vous obtenez le résultat 'inattendu'.

array = [2, 8, 22]
print(list(g))  # [8]
6
Eugene Yarmash

lorsque vous créez pour la première fois le tableau et assignez les éléments qu'il contient, les éléments du tableau pointent sur un emplacement de mémoire et le générateur conserve cet emplacement (pas le tableau) pour son exécution.

mais lorsque vous modifiez ses éléments du tableau, il est modifié, mais comme '8' est commun aux deux, python ne le réaffecte pas et pointe sur le même élément après modification.

Regardez l'exemple ci-dessous pour une meilleure compréhension

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

Sortie

140208067495680
140208067495904
140208067496128
<======>
140208067495712
140208067495904 # memory location is still same
140208067496352
[2, 8, 22]
[8]
1
ansu5555

En fait, ce n’est pas vraiment fou si vous regardez plus attentivement . 

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

il créera un générateur qui examinera le tableau et cherchera si le nombre de valeurs déjà existantes est supérieur à zéro. de sorte que votre générateur ne recherche que 1, 8 et 15, et lorsque vous changez les valeurs, le générateur recherche uniquement les valeurs précédentes, pas les nouvelles. car il (générateur) crée quand array les avait.

donc, si vous mettez des milliers de valeurs dans le tableau, il ne recherche que ces trois valeurs.

0
Mehrdad Pedramfar

La confusion, et donc la réponse, se trouve dans la ligne: g = (x for x in array if array.count(x) > 0)
Si nous simplifions cette ligne, elle deviendra: g = (x for x in array1 if array2.count(x) > 0)

Maintenant, lorsque générateur _ __ est créé, il conserve la référence de l'objet array1. Ainsi, même si je modifie la valeur de array1 par une autre valeur (c’est-à-dire le définit sur un nouvel objet tableau), cela n’affectera pas la copie de générateur de array1. Parce que seul array1 change sa référence d'objet. Mais array2 est vérifié dynamiquement. Donc, si nous changeons sa valeur, cela sera reflété.

Vous pouvez voir le résultat du code suivant pour le comprendre. Voir/ travail en ligne ici

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

Sortie: 

Old `array1` object ID: 47770072262024
Old `array2` object ID: 47770072263816
New `array1` object ID: 47770072263944
New `array2` object ID: 47770072264008
[1, 8]
0
cse