web-dev-qa-db-fra.com

Python classe accessible par itérateur et index

Peut-être une question n00b, mais j'ai actuellement une classe qui implémente un itérateur afin que je puisse faire quelque chose comme

for i in class():

mais je veux pouvoir accéder à la classe par index aussi bien

class()[1]

Comment puis je faire ça?

Merci!

43
xster

Implémentez les méthodes __iter__() et __getitem__() et autres.

50

Le courant réponse acceptée de @Ignacio Vazquez-Abrams est suffisant. Cependant, d'autres personnes intéressées par cette question peuvent envisager d'hériter leur classe d'une classe de base abstraite (ABC) (comme celles trouvées dans le module standard collections.abc ). Cela fait un certain nombre de choses ( il y en a probablement aussi d'autres ):

  • s'assure que toutes les méthodes dont vous avez besoin pour traiter votre objet "comme un ____" sont là
  • il est auto-documenté, en ce sens que quelqu'un qui lit votre code est capable de savoir instantanément que vous avez l'intention que votre objet "agisse comme un ____".
  • permet à isinstance(myobject,SomeABC) de fonctionner correctement.
  • fournit souvent des méthodes de manière auto-magique afin que nous n'ayons pas à les définir nous-mêmes

(Notez que, en plus de ce qui précède, créer votre propre ABC peut vous permettre de tester la présence d'une méthode spécifique ou d'un ensemble de méthodes dans n'importe quel objet, et basé sur sur ceci pour déclarer que cet objet est une sous-classe du ABC, même si l'objet n'hérite pas du ABCdirectement. Voir cette réponse pour plus d'informations. )


Exemple: implémentez une classe en lecture seule de type list en utilisant ABC

Maintenant, à titre d'exemple, choisissons et implémentons un ABC pour la classe dans la question d'origine. Il y a deux exigences:

  1. la classe est itérable
  2. accéder à la classe par index

De toute évidence, cette classe va être une sorte de collection. Donc, ce que nous allons faire est de regarder notre menu de collection ABC's pour trouver le ABC approprié (notez qu'il y a aussi numeric ABC ). Le ABC approprié dépend des méthodes abstraites que nous souhaitons utiliser dans notre classe.

Nous voyons qu'un Iterable est ce que nous recherchons si nous voulons utiliser la méthode __iter__(), ce dont nous avons besoin pour faire des choses comme for o in myobject:. Cependant, un Iterable n'inclut pas la méthode __getitem__(), ce dont nous avons besoin pour faire des choses comme myobject[i]. Nous aurons donc besoin d'utiliser un ABC différent.

Dans le menu collections.abc Des classes de base abstraites, nous voyons qu'un Sequence est le ABC le plus simple pour offrir les fonctionnalités dont nous avons besoin. Et - pourriez-vous regarder cela - nous obtenons la fonctionnalité Iterable comme méthode de mixage - ce qui signifie que nous n'avons pas à la définir nous-mêmes - gratuitement! Nous obtenons également __contains__, __reversed__, index et count. Ce qui, si vous y réfléchissez, est tout ce qui devrait être inclus dans tout objet indexé. Si vous aviez oublié de les inclure, les utilisateurs de votre code (y compris, potentiellement, vous-même!) Pourraient être assez ennuyés (je sais que je le ferais).

Cependant, il existe un deuxième ABC qui offre également cette combinaison de fonctionnalités (itérable et accessible par []): A Mapping . Lequel voulons-nous utiliser?

Nous rappelons que l'exigence est de pouvoir accéder à l'objet par index (comme un list ou un Tuple), c'est-à-dire pas par clé (comme un dict). Par conséquent, nous sélectionnons Sequence au lieu de Mapping.


Barre latérale: Il est important de noter qu'un Sequence est en lecture seule (tout comme un Mapping), il ne nous permettra donc pas de faire des choses comme myobject[i] = value Ou random.shuffle(myobject). Si nous voulons pouvoir faire des choses comme ça, nous devons continuer dans le menu de ABCs et utiliser un MutableSequence (ou un MutableMapping ), ce qui nécessitera l'implémentation de plusieurs méthodes supplémentaires.


Exemple de code

Maintenant, nous pouvons faire notre classe. Nous le définissons et le faisons hériter de Sequence.

from collections.abc import Sequence

class MyClass(Sequence):
    pass

Si nous essayons de l'utiliser, l'interpréteur nous dira quelles méthodes nous devons implémenter avant qu'il puisse être utilisé (notez que les méthodes sont également répertoriées sur la page de documentation Python docs):

>>> myobject = MyClass()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods __getitem__, __len__

Cela nous dit que si nous allons de l'avant et implémentons __getitem__ Et __len__, Nous pourrons utiliser notre nouvelle classe. Nous pourrions le faire comme ceci dans Python 3:

from collections.abc import Sequence

class MyClass(Sequence):
    def __init__(self,L):
        self.L = L
        super().__init__()
    def __getitem__(self, i):
        return self.L[i]
    def __len__(self):
        return len(self.L)

# Let's test it:
myobject = MyClass([1,2,3])
try:
    for idx,_ in enumerate(myobject):
        print(myobject[idx])
except Exception:
    print("Gah! No good!")
    raise
# No Errors!

ça marche!

69
Rick Teachey