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]
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
À 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 leftmostfor
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]
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]
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.
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]