Risposte:
Gli oggetti Iterator in Python sono conformi al protocollo iteratore, il che significa sostanzialmente che forniscono due metodi: __iter__()
e __next__()
.
Il __iter__
restituisce l'oggetto iteratore ed è implicitamente chiamato all'inizio del loop.
Il __next__()
metodo restituisce il valore successivo e viene chiamato implicitamente ad ogni incremento del ciclo. Questo metodo solleva un'eccezione StopIteration quando non ci sono più valori da restituire, che viene implicitamente catturato dai costrutti di loop per fermare l'iterazione.
Ecco un semplice esempio di contatore:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
Questo stamperà:
3
4
5
6
7
8
È più facile scrivere usando un generatore, come spiegato in una risposta precedente:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
L'output stampato sarà lo stesso. Sotto il cofano, l'oggetto generatore supporta il protocollo iteratore e fa qualcosa di approssimativamente simile alla classe Counter.
L'articolo di David Mertz, Iterators and Simple Generators , è una buona introduzione.
__next__
. counter
è un iteratore, ma non è una sequenza. Non memorizza i suoi valori. Ad esempio, non dovresti usare il contatore in un ciclo for doppio annidato.
__iter__
(oltre a in __init__
). Altrimenti, l'oggetto può essere ripetuto una sola volta. Ad esempio, se dici ctr = Counters(3, 8)
, non puoi usarlo for c in ctr
più di una volta.
Counter
è un iteratore e gli iteratori dovrebbero essere ripetuti una sola volta. Se si ripristina self.current
in__iter__
, poi un ciclo nidificato sul Counter
sarebbe completamente rotto, e ogni sorta di comportamenti assunti di iteratori (che chiamare iter
su di essi è idempotente) sono violati. Se vuoi essere in grado di iterare ctr
più di una volta, deve essere un iterabile non iteratore, dove restituisce un iteratore nuovo ogni volta che __iter__
viene invocato. Cercare di combinare (un iteratore che viene reimpostato implicitamente quando __iter__
viene invocato) viola i protocolli.
Counter
dovesse essere un iterabile non iteratore, rimuoveresti la definizione di __next__
/ next
interamente e probabilmente ridefiniresti __iter__
come funzione generatore dello stesso modulo del generatore descritto alla fine di questa risposta (tranne invece che i limiti provenienti da argomenti __iter__
, sarebbero argomenti su cui __init__
salvare self
e ai quali si può accedereself
a __iter__
).
Esistono quattro modi per creare una funzione iterativa:
__iter__
e__next__
(o next
in Python 2.x))__getitem__
)Esempi:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
Per vedere tutti e quattro i metodi in azione:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Che si traduce in:
A B C D E
A B C D E
A B C D E
A B C D E
Nota :
I due tipi di generatore ( uc_gen
e uc_genexp
) non possono essere reversed()
; il semplice iteratore ( uc_iter
) avrebbe bisogno del __reversed__
metodo magico (che, secondo i documenti , deve restituire un nuovo iteratore, ma restituendo le self
opere (almeno in CPython)); e getitem iteratable ( uc_getitem
) deve avere il __len__
metodo magico:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
Per rispondere alla domanda secondaria del colonnello Panic su un iteratore infinitamente valutato pigramente, ecco questi esempi, usando ciascuno dei quattro metodi sopra:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
Il risultato è (almeno per la mia esecuzione di esempio):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
Come scegliere quale usare? Questa è principalmente una questione di gusti. I due metodi che vedo più spesso sono i generatori e il protocollo iteratore, nonché un ibrido (che __iter__
restituisce un generatore).
Le espressioni del generatore sono utili per sostituire la comprensione dell'elenco (sono pigre e quindi possono risparmiare risorse).
Se si necessita di compatibilità con le versioni precedenti di Python 2.x, utilizzare __getitem__
.
uc_iter
dovrebbe scadere al termine (altrimenti sarebbe infinita); se vuoi farlo di nuovo devi ottenere un nuovo iteratore chiamando di uc_iter()
nuovo.
self.index = 0
in __iter__
modo da poter ripetere più volte. Altrimenti non puoi.
Innanzitutto il modulo itertools è incredibilmente utile per tutti i tipi di casi in cui un iteratore sarebbe utile, ma qui è tutto ciò che serve per creare un iteratore in Python:
dare la precedenza
Non è fantastico? Il rendimento può essere utilizzato per sostituire un rendimento normale in una funzione. Restituisce l'oggetto lo stesso, ma invece di distruggere lo stato e di uscire, salva lo stato per quando si desidera eseguire la successiva iterazione. Ecco un esempio in azione estratto direttamente dall'elenco delle funzioni di itertools :
def count(n=0):
while True:
yield n
n += 1
Come indicato nella descrizione delle funzioni (è la funzione count () dal modulo itertools ...), produce un iteratore che restituisce numeri interi consecutivi a partire da n.
Le espressioni dei generatori sono tutta un'altra lattina di worm (fantastici worm!). Possono essere utilizzati al posto di un Comprensione elenco per risparmiare memoria (le comprensioni dell'elenco creano un elenco in memoria che viene distrutto dopo l'uso se non assegnato a una variabile, ma le espressioni del generatore possono creare un oggetto generatore ... che è un modo elegante di dicendo Iteratore). Ecco un esempio di definizione di un'espressione del generatore:
gen = (n for n in xrange(0,11))
Questo è molto simile alla nostra definizione di iteratore sopra, tranne per il fatto che l'intera gamma è predeterminata tra 0 e 10.
Ho appena trovato xrange () (sorpreso di non averlo mai visto prima ...) e l'ho aggiunto all'esempio sopra. xrange () è una versione iterabile di range () che ha il vantaggio di non precompilare l'elenco. Sarebbe molto utile se avessi un corpus gigantesco di dati su cui scorrere e avessi solo tanta memoria per farlo.
Vedo alcuni di voi che ci fanno return self
dentro __iter__
. Volevo solo notare che esso __iter__
stesso può essere un generatore (eliminando così la necessità __next__
e sollevando StopIteration
eccezioni)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Naturalmente qui si potrebbe anche creare direttamente un generatore, ma per classi più complesse può essere utile.
return self
in __iter__
. Quando stavo per provare ad usarlo yield
ho trovato il tuo codice che faceva esattamente quello che volevo provare.
next()
? return iter(self).next()
?
self.current
o di qualsiasi altro contatore. Questa dovrebbe essere la risposta più votata!
iter
a istanze della classe, ma non sono esse stesse istanze della classe.
Questa domanda riguarda gli oggetti iterabili, non gli iteratori. In Python, anche le sequenze sono iterabili, quindi un modo per creare una classe iterabile è farla comportare come una sequenza, ovvero dargli __getitem__
e __len__
metodi. Ho provato questo su Python 2 e 3.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
__len__()
metodo. __getitem__
da solo con il comportamento previsto è sufficiente.
Tutte le risposte in questa pagina sono davvero fantastiche per un oggetto complesso. Ma per quelli contenenti incorporato tipi iterabili come attributi, come str
, list
, set
o dict
, o di qualsiasi implementazione di collections.Iterable
, è possibile omettere certe cose nella vostra classe.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in self.string)
# or simply
return self.string.__iter__()
# also
return iter(self.string)
Può essere usato come:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
.
Questa è una funzione iterabile senza yield
. Fa uso della iter
funzione e di una chiusura che mantiene il suo stato in un mutable ( list
) nell'ambito compreso in python 2.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
Per Python 3, lo stato di chiusura viene mantenuto immutabile nell'ambito compreso e nonlocal
viene utilizzato nell'ambito locale per aggiornare la variabile di stato.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Test;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
, ma solo per essere chiari: è più complesso e meno efficiente del semplice utilizzo di una yield
funzione di generatore basata; Python ha un sacco di supporto per interpreti per yield
funzioni di generatore basate che non puoi sfruttare qui, rendendo questo codice significativamente più lento. Tuttavia votato.
Se stai cercando qualcosa di breve e semplice, forse ti basterà:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
esempio di utilizzo:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Ispirato dalla risposta di Matt Gregory qui è un iteratore un po 'più complicato che restituirà a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
class AlphaCounter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
alpha = ' abcdefghijklmnopqrstuvwxyz'
n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
if n_current > n_high:
raise StopIteration
else:
increment = True
ret = ''
for x in self.current[::-1]:
if 'z' == x:
if increment:
ret += 'a'
else:
ret += 'z'
else:
if increment:
ret += alpha[alpha.find(x)+1]
increment = False
else:
ret += x
if increment:
ret += 'a'
tmp = self.current
self.current = ret[::-1]
return tmp
for c in AlphaCounter('a', 'zzz'):
print(c)