Come implementare __iter __ (self) per un oggetto contenitore (Python)


115

Ho scritto un oggetto contenitore personalizzato.

Secondo questa pagina , devo implementare questo metodo sul mio oggetto:

__iter__(self)

Tuttavia, dopo aver seguito il collegamento ai tipi di iteratore nel manuale di riferimento di Python, non vengono forniti esempi su come implementare il proprio.

Qualcuno può pubblicare uno snippet (o un collegamento a una risorsa), che mostra come farlo?

Il contenitore che sto scrivendo è una mappa (cioè memorizza i valori con chiavi univoche). i dict possono essere iterati in questo modo:

for k, v in mydict.items()

In questo caso devo essere in grado di restituire due elementi (una tupla?) Nell'iteratore. Non è ancora chiaro come implementare tale iteratore (nonostante le numerose risposte che sono state gentilmente fornite). Qualcuno potrebbe far luce su come implementare un iteratore per un oggetto contenitore simile a una mappa? (cioè una classe personalizzata che agisce come un dict)?

Risposte:


120

Normalmente userei una funzione di generatore. Ogni volta che usi una dichiarazione di rendimento, aggiungerà un elemento alla sequenza.

Quanto segue creerà un iteratore che restituisce cinque e quindi ogni elemento in some_list.

def __iter__(self):
   yield 5
   yield from some_list

Prima della 3.3, yield fromnon esisteva, quindi avresti dovuto fare:

def __iter__(self):
   yield 5
   for x in some_list:
      yield x

È necessario aumentare StopIteration? Come fa questo a distinguere quando fermarsi?
Jonathan

1
@JonathanLeaders Quando tutti gli elementi some_listsono stati ceduti.
laike9m

In realtà, con questo caso d'uso, è necessario aumentare StopIteration solo se si desidera interrompere la produzione di valori prima che some_list si esaurisca.
Tim Peoples,

21
StopIterationviene automaticamente sollevato da Python quando la funzione del generatore ritorna, o chiamando esplicitamente returno raggiungendo la fine della funzione (che come tutte le funzioni ha un implicito return Nonealla fine). Il rilancio esplicito StopIterationnon è necessario e da Python 3.5 effettivamente non funzionerà (vedi PEP 479 ): proprio come i generatori si trasformano returnin StopIteration, si trasformano esplicitamente raise StopIterationin un file RuntimeError.
Arthur Tacca

28

Un'altra opzione è ereditare dalla classe base astratta appropriata dal modulo `collections come documentato qui .

Nel caso in cui il contenitore sia il proprio iteratore, puoi ereditare da collections.Iterator. nextAllora devi solo implementare il metodo.

Un esempio è:

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

Mentre si sta guardando il collectionsmodulo, considerare che eredita da Sequence, Mappingo un'altra classe di base astratta se questo è più appropriato. Ecco un esempio per una Sequencesottoclasse:

>>> 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 : Grazie a Glenn Maynard per aver attirato la mia attenzione sulla necessità di chiarire la differenza tra iteratori da un lato e contenitori che sono iterabili piuttosto che iteratori dall'altro.


13
Non confondere oggetti iterabili e iteratori - non vuoi ereditare da Iterator per un oggetto iterabile che non è un iteratore stesso (ad esempio un contenitore).
Glenn Maynard

@ Glenn: Hai ragione sul fatto che un tipico contenitore non è un iteratore. Ho appena seguito la domanda, che menziona i tipi di iteratore. Penso che sia più appropriato ereditare da un'opzione più adatta come ho detto alla fine della risposta. Chiarirò questo punto nella risposta.
Muhammad Alkarouri

13

di solito __iter__()restituisce self se hai già definito il metodo next () (oggetto generatore):

ecco un esempio fittizio di un generatore:

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

ma __iter__()può anche essere usato in questo modo: http://mail.python.org/pipermail/tutor/2006-January/044455.html


7
Questo è quello che fai per le classi iteratore, ma la domanda riguarda gli oggetti contenitore.
Glenn Maynard

11

Se il tuo oggetto contiene un insieme di dati a cui vuoi associare l'iter del tuo oggetto, puoi imbrogliare e fare questo:

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

2
Invece di controllare hasattr, usatry/except AttributeError
IceArdor

9

L '"interfaccia iterabile" in python consiste di due metodi __next__()e __iter__(). La __next__funzione è la più importante, poiché definisce il comportamento dell'iteratore, ovvero la funzione determina quale valore deve essere restituito dopo. Il __iter__()metodo viene utilizzato per ripristinare il punto di inizio dell'iterazione. Spesso scoprirai che __iter__()può semplicemente restituire se stesso quando __init__()viene utilizzato per impostare il punto di partenza.

Vedere il codice seguente per definire una classe inversa che implementa l '"interfaccia iterabile" e definisce un iteratore su qualsiasi istanza da qualsiasi classe di sequenza. Il __next__()metodo inizia alla fine della sequenza e restituisce i valori in ordine inverso rispetto alla sequenza. Si noti che le istanze di una classe che implementa l '"interfaccia della sequenza" devono definire __len__()un __getitem__()metodo e.

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

7

Per rispondere alla domanda sulle mappature : il tuo fornito __iter__dovrebbe iterare sui tasti della mappatura. Quello che segue è un semplice esempio che crea una mappatura x -> x * xe funziona su Python3 estendendo la mappatura 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

4

Nel caso in cui non desideri ereditare da dictcome altri hanno suggerito, ecco la risposta diretta alla domanda su come implementare __iter__per un semplice esempio di un dict personalizzato:

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

Questo utilizza un generatore, che è ben descritto qui .

Poiché ereditiamo da Mapping, devi anche implementare __getitem__e __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)

2

Un'opzione che potrebbe funzionare in alcuni casi è fare in modo che la tua classe personalizzata erediti da dict. Questa sembra una scelta logica se si comporta come un dict; forse dovrebbe essere una regola. In questo modo, ottieni un'iterazione dict-like gratuitamente.

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

Produzione:

Some name
a => 1
b => 2

2

esempio per inhert from dict, modificarne iter, ad esempio, il tasto skip 2quando si è in ciclo 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.