web-dev-qa-db-fra.com

Comportement étrange Python avec énumérer

Je sais que je ne suis pas censé modifier la liste dans une boucle, mais par curiosité, j'aimerais savoir pourquoi le nombre d'itérations est différent entre les deux exemples suivants.

Exemple 1:

x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    del x[0]
    print(i, s, x)

Exemple 2:

x = [1,2,3,4,5]
for i, s in enumerate(x):
    x = [1]
    print(i, s, x)

L'exemple 1 ne s'exécute que 3 fois, car lorsque i==3, len(x)==2

L'exemple 2 est exécuté 5 fois même si len(x)==1.

Ma question est donc la suivante: enumerate génère-t-il une liste complète de paires (index, value) au début de la boucle et effectue-t-il une itération? Ou sont-ils générés à chaque itération de la boucle?

18
dbdq

enumerate () renvoie un itérateur ou un autre objet prenant en charge l'itération. La méthode __next __ () de l'itérateur renvoyée par enumerate () renvoie un Tuple contenant un décompte (depuis le début, la valeur par défaut étant 0) et les valeurs obtenu en itérant sur (iterable} _.

__next __ () renvoie le prochain élément du conteneur. S'il n'y a pas d'autres éléments, déclenchez l'exception StopIteration.

Enumerate () génère-t-il une liste complète des paires (index, valeur) au début de la boucle et effectue une itération à travers celle-ci? Ou sont-ils générés à chaque itération de la boucle? 

Donc, enumerate() renvoie un itérateur et à chaque itération, __next__() vérifie s’il existe d’autres éléments. enumerate() ne crée pas une liste complète au début de la boucle. 

En tant que, @Wisperwind mentionné, dans votre deuxième cas, vous attribuez un nouvel objet au nom x. L'objet, la boucle itérée, ne change pas pendant l'itération.

18
Wasi Ahmad

Dans le premier exemple, vous modifiez en fait la liste sur laquelle vous effectuez une itération.

D'autre part, dans le deuxième cas, vous n'attribuez qu'un nouvel objet au nom x. L'objet sur lequel la boucle itère ne change pas, cependant.

Consultez http://foobarnbaz.com/2012/07/08/understanding-python-variables/ pour une explication plus détaillée des noms et des variables en Python.

22
Wisperwind

Juste une clarification de ce que Wasi Ahmad et Wisperwind ont dit. Les deux indiquent que "vous n'attribuez qu'un nouvel objet au nom x". Cela pourrait être légèrement déroutant car cela pourrait être interprété comme disant: "vous créez un nouvel objet ([1]) et le stockez sous le nom x, auquel vous dites" Eh oui, alors pourquoi ne change-t-il pas?! "Pour voir ce qui se passe, affiche l'identifiant de l'objet 

x = [1, 2, 3, 4, 5]
y = x  # To keep a reference to the original list
print id(x), id(y)
for i, v in enumerate(x):
    x = [1]
    print id(x), id(y)
print id(x), id(y)


# output (somewhat contrived as I don't have a python environment set up)
#    X ID            Y ID
10000000000001 10000000000001
10000000000002 10000000000001
10000000000003 10000000000001
10000000000004 10000000000001
10000000000005 10000000000001
10000000000006 10000000000001
10000000000006 10000000000001

Vous remarquerez que la id de x change à chaque fois dans la boucle et lorsque vous avez terminé avec la boucle, x pointe vers la dernière modification effectuée dans la boucle. Lorsque vous parcourez votre boucle, elle parcourt l'occurrence originale de x, que vous puissiez ou non la référencer. 

Comme vous pouvez le constater, y pointe sur l'original x. Lorsque vous effectuez vos itérations dans la boucle, même si x est en train de changer, y pointe toujours sur la x originale qui est toujours en train d'être bouclée.

8
FuriousGeorge

En effet: votre premier extrait modifie la liste itérée en place; le second pointe la variable x vers une nouvelle liste, en laissant inchangée la liste traversée par enumerate(). Vous pouvez le voir en action en visitant les liens suivants sur www.pythontutor.com, qui vous permettent de passer en revue votre code et de visualiser le contenu de vos variables:

Pour mieux voir ce qui se passe, allez ici au lieu de passer en revue le code développé suivant:

x = [1,2,3,4,5]
view = enumerate(x)
for i, s in view:
    x = [1]
    print(i, s, x)
1
alexis

D'autres ont déjà signalé que votre deuxième exemple modifie uniquement la valeur sur laquelle pointe x, mais pas la liste sur laquelle vous effectuez une itération. Ceci est un exemple parfait de la différence entre une affectation ordinaire (x = [1]) et affectation de tranche (x[:] = [1]). Ce dernier modifie la liste x points vers in-place :

x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    x[:] = [1]
    print(i, s, x)

imprimera

(0, 1, [1])
1
Florian Brucker
x = [1, 2, 3, 4, 5]

La liste [1, 2, 3, 4, 5] est 'taggée' avec x

for i, s in enumerate(x):

enumerate () attache une autre balise, donc [1, 2, 3, 4, 5] est maintenant tagué x et y. enumerate () continuera à utiliser la balise y, pas la balise x

del x[0]

La liste stockée en mémoire est modifiée, donc x et y font maintenant référence à [2, 3, 4, 5]

Alternativement, lorsque vous utilisez

x = [1]

Une nouvelle liste [1] est créée en mémoire et la balise x pointe maintenant sur celle-ci. La balise y pointe toujours sur la liste d'origine.

Comment fonctionne la variable Python:
http://foobarnbaz.com/2012/07/08/understanding-python-variables/

0
JeremiahBarrar