So implementieren Sie __iter __ (self) für ein Containerobjekt (Python)

115

Ich habe ein benutzerdefiniertes Containerobjekt geschrieben.

Laut dieser Seite muss ich diese Methode auf meinem Objekt implementieren:

__iter__(self)

Wenn Sie jedoch dem Link zu Iteratortypen im Python-Referenzhandbuch folgen , werden keine Beispiele für die Implementierung Ihrer eigenen Typen angegeben.

Kann jemand ein Snippet (oder einen Link zu einer Ressource) posten, das zeigt, wie das geht?

Der Container, den ich schreibe, ist eine Karte (dh speichert Werte durch eindeutige Schlüssel). Diktate können folgendermaßen wiederholt werden:

for k, v in mydict.items()

In diesem Fall muss ich in der Lage sein, zwei Elemente (ein Tupel?) Im Iterator zurückzugeben. Es ist immer noch nicht klar, wie ein solcher Iterator implementiert werden soll (trotz der verschiedenen Antworten, die freundlicherweise zur Verfügung gestellt wurden). Könnte jemand bitte etwas mehr Licht in die Implementierung eines Iterators für ein kartenähnliches Containerobjekt bringen? (dh eine benutzerdefinierte Klasse, die sich wie ein Diktat verhält)?

skyeagle
quelle

Antworten:

120

Normalerweise würde ich eine Generatorfunktion verwenden. Jedes Mal, wenn Sie eine Yield-Anweisung verwenden, wird der Sequenz ein Element hinzugefügt.

Im Folgenden wird ein Iterator erstellt, der fünf und dann jedes Element in some_list ergibt.

def __iter__(self):
   yield 5
   yield from some_list

Pre-3.3 gab yield fromes nicht, also müssten Sie Folgendes tun:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x
mikerobi
quelle
Muss man StopIteration erhöhen? Wie unterscheidet dies, wann aufzuhören ist?
Jonathan
1
@ JonathanLeaders Wenn alle Elemente in some_listergeben wurden.
Laike9m
In diesem Anwendungsfall müssen Sie StopIteration nur dann erhöhen, wenn Sie keine Werte mehr liefern möchten, bevor diese some_list erschöpft sind.
Tim Peoples
21
StopIterationwird von Python automatisch ausgelöst, wenn die Generatorfunktion zurückkehrt, entweder durch expliziten Aufruf returnoder durch Erreichen des Endes der Funktion (die wie alle Funktionen return Noneam Ende ein implizites Element hat ). Ein explizites Erhöhen StopIterationist nicht erforderlich und ab Python 3.5 funktioniert es tatsächlich nicht mehr (siehe PEP 479 ): Genau wie Generatoren returnwerden StopIterationsie explizit raise StopIterationzu a RuntimeError.
Arthur Tacca
28

Eine andere Möglichkeit besteht darin, von der entsprechenden abstrakten Basisklasse aus dem hier dokumentierten Modul `collection zu erben .

Falls der Container ein eigener Iterator ist, können Sie von erben collections.Iterator. Sie müssen die nextMethode dann nur noch implementieren .

Ein Beispiel ist:

>>> 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

Wenn Sie sich das collectionsModul ansehen Sequence, sollten Sie in Betracht ziehen , von Mappingoder einer anderen abstrakten Basisklasse zu erben , wenn dies angemessener ist. Hier ist ein Beispiel für eine SequenceUnterklasse:

>>> 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 : Vielen Dank an Glenn Maynard, der mich darauf aufmerksam gemacht hat, dass der Unterschied zwischen Iteratoren einerseits und Containern, die eher iterabel als iterativ sind, geklärt werden muss.

Muhammad Alkarouri
quelle
13
Verwechseln Sie keine iterierbaren Objekte und Iteratoren - Sie möchten nicht von Iterator für ein iterierbares Objekt erben, das selbst kein Iterator ist (z. B. ein Container).
Glenn Maynard
@Glenn: Sie haben Recht, dass ein typischer Container kein Iterator ist. Ich bin gerade der Frage gefolgt, in der die Iteratortypen erwähnt werden. Ich denke, es ist angemessener, von einer geeigneteren Option zu erben, wie ich am Ende der Antwort sagte. Ich werde diesen Punkt in der Antwort klarstellen.
Muhammad Alkarouri
13

__iter__()Geben Sie normalerweise nur self zurück, wenn Sie bereits die next () -Methode (Generatorobjekt) definiert haben:

Hier ist ein Dummy-Beispiel eines Generators:

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

sondern __iter__()kann auch wie folgt verwendet werden: http://mail.python.org/pipermail/tutor/2006-January/044455.html

Mouad
quelle
7
Das tun Sie für Iteratorklassen, aber die Frage betrifft Containerobjekte.
Glenn Maynard
11

Wenn Ihr Objekt einen Datensatz enthält, an den Sie den Iter Ihres Objekts binden möchten, können Sie Folgendes tun:

>>> 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
quelle
2
Anstatt zu überprüfen hasattr, verwenden Sietry/except AttributeError
IceArdor
9

Die "iterable Schnittstelle" in Python besteht aus zwei Methoden __next__()und __iter__(). Die __next__Funktion ist die wichtigste, da sie das Iteratorverhalten definiert. Das heißt, die Funktion bestimmt, welcher Wert als nächstes zurückgegeben werden soll. Die __iter__()Methode wird verwendet, um den Startpunkt der Iteration zurückzusetzen. Oft werden Sie feststellen, dass Sie __iter__()sich einfach selbst zurückgeben können, wenn Sie __init__()den Startpunkt festlegen.

Im folgenden Code finden Sie Informationen zum Definieren einer Klassenumkehr, die die "iterierbare Schnittstelle" implementiert und einen Iterator über eine beliebige Instanz einer beliebigen Sequenzklasse definiert. Die __next__()Methode beginnt am Ende der Sequenz und gibt Werte in umgekehrter Reihenfolge der Sequenz zurück. Beachten Sie, dass Instanzen einer Klasse, die die "Sequenzschnittstelle" implementiert, eine __len__()und eine __getitem__()Methode definieren müssen .

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
quelle
7

Um die Frage zu Zuordnungen zu beantworten : Ihre bereitgestellten __iter__sollten die Schlüssel der Zuordnung durchlaufen . Das folgende Beispiel zeigt ein einfaches Mapping x -> x * xund arbeitet mit Python3, um das ABC-Mapping zu erweitern.

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
quelle
4

Falls Sie nicht erben möchten, dictwie andere vorgeschlagen haben, finden Sie hier eine direkte Antwort auf die Frage, wie Sie __iter__ein grobes Beispiel für ein benutzerdefiniertes Diktat implementieren können :

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

Das nutzt einen Generator, der gut beschrieben ist hier .

Da wir von erben Mapping, müssen Sie auch implementieren __getitem__und __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
quelle
2

Eine Option , die für manche Fälle funktionieren könnte zu Ihrer benutzerdefinierten Klasse machen erbt aus dict. Dies scheint eine logische Entscheidung zu sein, wenn es sich wie ein Diktat verhält. Vielleicht sollte es ein Diktat sein. Auf diese Weise erhalten Sie kostenlos eine diktierte Iteration.

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

Ausgabe:

Some name
a => 1
b => 2
Eddifiziert
quelle
2

Beispiel für das Erben von dict, ändern Sie die iterÜberspring-Taste, 2wenn Sie sich in der for-Schleife befinden

# 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
quelle