web-dev-qa-db-fra.com

Pourquoi la définition de __getitem__ sur une classe la rend-elle itérable en python?

Pourquoi la définition de __getitem__ sur une classe la rend-elle itérable?

Par exemple, si j'écris:

class b:
  def __getitem__(self, k):
    return k

cb = b()

for k in cb:
  print k

J'obtiens la sortie:

0
1
2
3
4
5
6
7
8
...

Je m'attendrais vraiment à voir une erreur renvoyée par "for k in cb:"

57
grieve

Si vous jetez un œil à PEP234 définissant les itérateurs, il dit:

1. An object can be iterated over with "for" if it implements
   __iter__() or __getitem__().

2. An object can function as an iterator if it implements next().
46
Edward Dale

Prise en charge d'itération pour __getitem__ peut être considéré comme une "fonction héritée" qui a permis une transition plus fluide lorsque PEP234 a introduit l'itérabilité comme concept principal. Il s'applique uniquement aux classes sans __iter__ dont __getitem__ accepte les entiers 0, 1, & c et lève IndexError une fois que l'index est trop élevé (si jamais), généralement des classes "séquence" codées avant __iter__ est apparu (bien que rien ne vous empêche de coder de nouvelles classes de cette façon aussi).

Personnellement, je préfère ne pas m'appuyer sur cela dans le nouveau code, bien qu'il ne soit pas obsolète ni ne disparaisse (fonctionne très bien dans Python 3 aussi), donc c'est juste une question de style et de goût ("explicite vaut mieux qu'implicite", je préfère donc explicitement prendre en charge l'itérabilité plutôt que de compter sur __getitem__ le soutenant implicitement pour moi - mais pas un bigge).

60
Alex Martelli

__getitem__ est antérieur au protocole itérateur, et était dans le passé le moyen niquement de rendre les choses itérables. En tant que tel, il est toujours pris en charge comme méthode d'itération. Essentiellement, le protocole d'itération est:

  1. Recherchez un __iter__ méthode. S'il existe, utilisez le nouveau protocole d'itération.

  2. Sinon, essayez d'appeler __getitem__ avec des valeurs entières successivement plus grandes jusqu'à ce qu'il déclenche IndexError.

(2) était le seul moyen de le faire, mais avait l'inconvénient qu'il supposait plus que ce qui était nécessaire pour prendre en charge juste l'itération. Pour prendre en charge l'itération, vous deviez prendre en charge l'accès aléatoire, qui était beaucoup plus cher pour des choses comme les fichiers ou les flux réseau où aller en avant était facile, mais revenir en arrière nécessiterait de tout stocker. __iter__ a permis l'itération sans accès aléatoire, mais comme l'accès aléatoire autorise généralement l'itération de toute façon, et parce que rompre la compatibilité descendante serait mauvais, __getitem__ est toujours pris en charge.

25
Brian

Méthodes spéciales telles que __getitem__ ajoute des comportements spéciaux aux objets, y compris l'itération.

http://docs.python.org/reference/datamodel.html#object. getitem

"pour les boucles, attendez qu'une IndexError soit levée pour les index illégaux afin de permettre une détection correcte de la fin de la séquence."

Soulevez IndexError pour signaler la fin de la séquence.

Votre code est fondamentalement équivalent à:

i = 0
while True:
    try:
        yield object[i]
        i += 1
    except IndexError:
        break

Où objet est ce que vous êtes en train d'itérer dans la boucle for.

6
FogleBird

Il en est ainsi pour des raisons historiques. Avant Python 2.2 __getitem__ était le seul moyen de créer une classe qui pouvait être répétée avec la boucle for. En 2.2, le protocole __iter__ a été ajouté mais pour conserver la compatibilité descendante, __getitem__ fonctionne toujours dans les boucles for. .

5
Ants Aasma

Parce que cb[0] Est identique à cb.__getitem__(0). Voir la documentation python à ce sujet.

2
Jorenko