Comment implémenter __iter __ (self) pour un objet conteneur (Python)

115

J'ai écrit un objet conteneur personnalisé.

Selon cette page , je dois implémenter cette méthode sur mon objet:

__iter__(self)

Cependant, après avoir suivi le lien vers les types d'itérateur dans le manuel de référence Python, aucun exemple n'est donné sur la façon d'implémenter le vôtre.

Quelqu'un peut-il publier un extrait (ou un lien vers une ressource), qui montre comment faire cela?

Le conteneur que j'écris est une carte (c'est-à-dire stocke les valeurs par des clés uniques). les dicts peuvent être itérés comme ceci:

for k, v in mydict.items()

Dans ce cas, j'ai besoin de pouvoir renvoyer deux éléments (un tuple?) Dans l'itérateur. On ne sait toujours pas comment implémenter un tel itérateur (malgré les nombreuses réponses qui ont été aimablement fournies). Quelqu'un pourrait-il s'il vous plaît éclairer davantage sur la façon d'implémenter un itérateur pour un objet conteneur de type carte? (c'est-à-dire une classe personnalisée qui agit comme un dict)?

skyeagle
la source

Réponses:

120

J'utiliserais normalement une fonction générateur. Chaque fois que vous utilisez une instruction yield, elle ajoute un élément à la séquence.

Ce qui suit va créer un itérateur qui donne cinq, puis chaque élément de some_list.

def __iter__(self):
   yield 5
   yield from some_list

La version antérieure à 3.3 yield fromn'existait pas, vous devriez donc faire:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x
mikerobi
la source
Doit-on déclencher StopIteration? Comment cela distingue-t-il quand s'arrêter?
Jonathan
1
@JonathanLeaders Lorsque tous les éléments de some_listont été générés.
laike9m
En fait, avec ce cas d'utilisation, vous n'avez besoin de lever StopIteration que si vous souhaitez arrêter de fournir des valeurs avant d' some_list être épuisé.
Tim Peoples
21
StopIterationest automatiquement déclenché par Python lorsque la fonction génératrice revient, soit en appelant explicitement, returnsoit en atteignant la fin de la fonction (qui, comme toutes les fonctions, a un implicite return Noneà la fin). Le relèvement explicite StopIterationn'est pas nécessaire et à partir de Python 3.5 ne fonctionnera pas (voir PEP 479 ): tout comme les générateurs se transforment returnen StopIteration, ils deviennent explicites raise StopIterationen RuntimeError.
Arthur Tacca
28

Une autre option consiste à hériter de la classe de base abstraite appropriée du module `collections comme documenté ici .

Si le conteneur est son propre itérateur, vous pouvez hériter de collections.Iterator. Il vous suffit alors d'implémenter la nextméthode.

Un exemple est:

>>> from collections import Iterator
>>> class MyContainer(Iterator):
...     def __init__(self, *data):
...         self.data = list(data)
...     def next(self):
...         if not self.data:
...             raise StopIteration
...         return self.data.pop()
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
4.0
3
two
1

Pendant que vous regardez le collectionsmodule, envisagez d'hériter de Sequence, Mappingou d'une autre classe de base abstraite si cela est plus approprié. Voici un exemple de Sequencesous - classe:

>>> from collections import Sequence
>>> class MyContainer(Sequence):
...     def __init__(self, *data):
...         self.data = list(data)
...     def __getitem__(self, index):
...         return self.data[index]
...     def __len__(self):
...         return len(self.data)
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
1
two
3
4.0

NB : Merci à Glenn Maynard d'avoir attiré mon attention sur la nécessité de clarifier la différence entre les itérateurs d'une part et les conteneurs itérables plutôt qu'itérateurs d'autre part.

Muhammad Alkarouri
la source
13
Ne mélangez pas les objets itérables et les itérateurs - vous ne voulez pas hériter d'Iterator pour un objet itérable qui n'est pas un itérateur lui-même (par exemple un conteneur).
Glenn Maynard
@Glenn: Vous avez raison de dire qu'un conteneur typique n'est pas un itérateur. J'ai juste suivi la question, qui mentionne les types d'itérateurs. Je pense qu'il est plus approprié d'hériter d'une option plus appropriée comme je l'ai dit à la fin de la réponse. Je clarifierai ce point dans la réponse.
Muhammad Alkarouri
13

généralement, __iter__()retournez simplement self si vous avez déjà défini la méthode next () (objet générateur):

voici un exemple factice de générateur:

class Test(object):

    def __init__(self, data):
       self.data = data

    def next(self):
        if not self.data:
           raise StopIteration
        return self.data.pop()

    def __iter__(self):
        return self

mais __iter__()peut également être utilisé comme ceci: http://mail.python.org/pipermail/tutor/2006-January/044455.html

mouad
la source
7
C'est ce que vous faites pour les classes itératrices, mais la question concerne les objets conteneurs.
Glenn Maynard
11

Si votre objet contient un ensemble de données auxquelles vous souhaitez lier l'itération de votre objet, vous pouvez tricher et faire ceci:

>>> class foo:
    def __init__(self, *params):
           self.data = params
    def __iter__(self):
        if hasattr(self.data[0], "__iter__"):
            return self.data[0].__iter__()
        return self.data.__iter__()
>>> d=foo(6,7,3,8, "ads", 6)
>>> for i in d:
    print i
6
7
3
8
ads
6
Squirrelsama
la source
2
Au lieu de vérifier hasattr, utiliseztry/except AttributeError
IceArdor
9

L '"interface itérative" en python se compose de deux méthodes __next__()et __iter__(). La __next__fonction est la plus importante, car elle définit le comportement de l'itérateur - c'est-à-dire que la fonction détermine la valeur à renvoyer ensuite. La __iter__()méthode est utilisée pour réinitialiser le point de départ de l'itération. Souvent, vous constaterez que cela __iter__()peut simplement revenir à soi quand il __init__()est utilisé pour définir le point de départ.

Consultez le code suivant pour définir une classe inverse qui implémente l '"interface itérative" et définit un itérateur sur n'importe quelle instance de n'importe quelle classe de séquence. La __next__()méthode commence à la fin de la séquence et renvoie les valeurs dans l'ordre inverse de la séquence. Notez que les instances d'une classe implémentant "l'interface de séquence" doivent définir __len__()une __getitem__()méthode et une .

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, seq):
        self.data = seq
        self.index = len(seq)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> rev = Reverse('spam')
>>> next(rev)   # note no need to call iter()
'm'
>>> nums = Reverse(range(1,10))
>>> next(nums)
9
FredAKA
la source
7

Pour répondre à la question sur les mappages : votre fourni __iter__doit itérer sur les clés du mappage. Ce qui suit est un exemple simple qui crée un mappage x -> x * xet fonctionne sur Python3 en étendant le mappage ABC.

import collections.abc

class MyMap(collections.abc.Mapping):
    def __init__(self, n):
        self.n = n

    def __getitem__(self, key): # given a key, return it's value
        if 0 <= key < self.n:
            return key * key
        else:
            raise KeyError('Invalid key')

    def __iter__(self): # iterate over all keys
        for x in range(self.n):
            yield x

    def __len__(self):
        return self.n

m = MyMap(5)
for k, v in m.items():
    print(k, '->', v)
# 0 -> 0
# 1 -> 1
# 2 -> 4
# 3 -> 9
# 4 -> 16
Juan A. Navarro
la source
4

Au cas où vous ne voudriez pas hériter dictcomme d'autres l'ont suggéré, voici la réponse directe à la question sur la façon de mettre en œuvre __iter__pour un exemple brut d'un dict personnalisé:

class Attribute:
    def __init__(self, key, value):
        self.key = key
        self.value = value

class Node(collections.Mapping):
    def __init__(self):
        self.type  = ""
        self.attrs = [] # List of Attributes

    def __iter__(self):
        for attr in self.attrs:
            yield attr.key

Cela utilise un générateur, qui est bien décrit ici .

Puisque nous héritons de Mapping, vous devez également implémenter __getitem__et __len__:

    def __getitem__(self, key):
        for attr in self.attrs:
            if key == attr.key:
                return attr.value
        raise KeyError

    def __len__(self):
        return len(self.attrs)
jfritz42
la source
2

Une option qui pourrait fonctionner dans certains cas consiste à faire hériter de votre classe personnalisée dict. Cela semble être un choix logique s'il agit comme un dict; peut-être que ça devrait être un dict. De cette façon, vous obtenez une itération de type dict gratuitement.

class MyDict(dict):
    def __init__(self, custom_attribute):
        self.bar = custom_attribute

mydict = MyDict('Some name')
mydict['a'] = 1
mydict['b'] = 2

print mydict.bar
for k, v in mydict.items():
    print k, '=>', v

Production:

Some name
a => 1
b => 2
Eddifié
la source
2

exemple pour hériter de dict, modifier sa iterclé, par exemple, skip 2lors de la boucle for

# method 1
class Dict(dict):
    def __iter__(self):
        keys = self.keys()
        for i in keys:
            if i == 2:
                continue
            yield i

# method 2
class Dict(dict):
    def __iter__(self):
        for i in super(Dict, self).__iter__():
            if i == 2:
                continue
            yield i
WeizhongTu
la source