Jak zaimplementować __iter __ (self) dla obiektu kontenera (Python)

115

Napisałem niestandardowy obiekt kontenera.

Zgodnie z tą stroną muszę zaimplementować tę metodę na moim obiekcie:

__iter__(self)

Jednak po przejrzeniu linku do typów Iteratorów w podręczniku Python nie ma przykładów implementacji własnego.

Czy ktoś może opublikować fragment (lub link do zasobu), który pokazuje, jak to zrobić?

Kontener, który piszę, jest mapą (tj. Przechowuje wartości według unikalnych kluczy). dykty można iterować w następujący sposób:

for k, v in mydict.items()

W takim przypadku muszę mieć możliwość zwrócenia dwóch elementów (krotki?) W iteratorze. Nadal nie jest jasne, jak zaimplementować taki iterator (pomimo kilku uprzejmych odpowiedzi). Czy mógłby ktoś rzucić więcej światła na to, jak zaimplementować iterator dla obiektu kontenera podobnego do mapy? (tj. klasa niestandardowa, która działa jak dykt)?

skyeagle
źródło

Odpowiedzi:

120

Normalnie użyłbym funkcji generatora. Za każdym razem, gdy użyjesz instrukcji yield, doda ona element do sekwencji.

Poniższe spowoduje utworzenie iteratora, który daje pięć, a następnie każdy element w some_list.

def __iter__(self):
   yield 5
   yield from some_list

Pre-3.3 yield fromnie istniał, więc musisz zrobić:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x
mikerobi
źródło
Czy trzeba podnosić StopIteration? Jak to odróżnia, kiedy przestać?
Jonathan,
1
@JonathanLeaders Gdy wszystkie elementy some_listzostały przekazane.
laike9m
Właściwie - w tym przypadku użycia - musisz podnieść StopIteration tylko wtedy, gdy chcesz przestać dostarczać wartości, zanim some_list zostanie wyczerpany.
Tim Peoples
21
StopIterationjest automatycznie wywoływana przez Pythona, gdy funkcja generatora zwraca, albo przez twoje jawne wywołanie, returnalbo przez osiągnięcie końca funkcji (która jak wszystkie funkcje ma return Nonena końcu niejawny ). Jawne podnoszenie StopIterationnie jest konieczne i od Pythona 3.5 faktycznie nie będzie działać (zobacz PEP 479 ): tak jak generatory zamieniają się returnw StopIterationjawne raise StopIterationpliki RuntimeError.
Arthur Tacca
28

Inną opcją jest dziedziczenie z odpowiedniej abstrakcyjnej klasy bazowej z modułu `collections, jak opisano tutaj .

Jeśli kontener jest własnym iteratorem, możesz dziedziczyć z collections.Iterator. Wystarczy wtedy zaimplementować nextmetodę.

Oto przykład:

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

Podczas patrzenia na collectionsmodule, należy rozważyć dziedziczenie z Sequence, Mappinglub inny abstrakcyjne klasy bazowej, jeżeli jest to bardziej właściwe. Oto przykład dla Sequencepodklasy:

>>> 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 : Dziękuję Glennowi Maynardowi za zwrócenie mojej uwagi na potrzebę wyjaśnienia różnicy między iteratorami z jednej strony a kontenerami, które są iterowalne, a nie iteratorami z drugiej.

Muhammad Alkarouri
źródło
13
Nie mieszaj obiektów iterowalnych i iteratorów - nie chcesz dziedziczyć z Iteratora dla iterowalnego obiektu, który sam nie jest iteratorem (np. Kontener).
Glenn Maynard
@Glenn: Masz rację, że typowy kontener nie jest iteratorem. Po prostu podążyłem za pytaniem, które wspomina o typach iteratorów. Myślę, że bardziej odpowiednie jest dziedziczenie z bardziej odpowiedniej opcji, jak powiedziałem na końcu odpowiedzi. Wyjaśnię ten punkt w odpowiedzi.
Muhammad Alkarouri
13

zwykle __iter__()po prostu zwraca self, jeśli już zdefiniowałeś metodę next () (obiekt generatora):

oto przykład Dummy generatora:

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

ale __iter__()może być również używany w ten sposób: http://mail.python.org/pipermail/tutor/2006-J January / 044455.html

mouad
źródło
7
To właśnie robisz dla klas iteratorów, ale pytanie dotyczy obiektów kontenerów.
Glenn Maynard
11

Jeśli twój obiekt zawiera zestaw danych, z którymi chcesz powiązać iter obiektu, możesz oszukać i zrobić to:

>>> 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
źródło
2
Zamiast sprawdzać hasattr, użyjtry/except AttributeError
IceArdor
10

„Iterowalny interfejs” w Pythonie składa się z dwóch metod __next__()i __iter__(). __next__Funkcja jest najważniejsza, jak to określa zachowanie iterator - czyli funkcja określa, jakie wartości powinny być zwrócone następnego. __iter__()Metoda służy do zresetowania punktu początkowego iteracji. Często okaże się, że __iter__()może po prostu wrócić do siebie, gdy __init__()jest używany do ustawienia punktu początkowego.

Zobacz poniższy kod, aby zdefiniować odwróconą klasę, która implementuje „iterowalny interfejs” i definiuje iterator dla dowolnej instancji z dowolnej klasy sekwencji. __next__()Sposób zaczyna się na końcu sekwencji i zwraca wartość w odwrotnej kolejności sekwencji. Zwróć uwagę, że instancje z klasy implementującej „interfejs sekwencji” muszą definiować a __len__()i __getitem__()metodę.

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
źródło
7

Aby odpowiedzieć na pytanie dotyczące mapowań : Twoje dane __iter__powinny iterować po kluczach mapowania. Poniżej znajduje się prosty przykład, który tworzy mapowanie x -> x * xi działa na Pythonie3, rozszerzając mapowanie 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
źródło
4

Jeśli nie chcesz dziedziczyć dicttak, jak sugerowali inni, oto bezpośrednia odpowiedź na pytanie, jak zaimplementować, __iter__aby uzyskać surowy przykład niestandardowego dyktowania:

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

To wykorzystuje generator, który jest dobrze opisany tutaj .

Ponieważ dziedziczymy po Mapping, musisz również zaimplementować __getitem__i __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
źródło
2

Jedną z opcji, która może działać w niektórych przypadkach, jest ustawienie dziedziczenia z klasy niestandardowej dict. Wydaje się to logicznym wyborem, jeśli zachowuje się jak dykt; może to powinien być dykt. W ten sposób otrzymujesz darmową iterację w stylu dyktowania.

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

Wynik:

Some name
a => 1
b => 2
Eddyfikowany
źródło
2

na przykład dla inhert from dict, zmodyfikuj jego iterklawisz, na przykład, skip key 2w pętli 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
źródło