Domanda

Come si creerebbe una funzione iterativa (o oggetto iteratore) in Python?

È stato utile?

Soluzione

Gli oggetti iteratori in Python sono conformi al protocollo iteratore, il che significa sostanzialmente che forniscono due metodi: __iter__() E next().IL __iter__ restituisce l'oggetto iteratore e viene chiamato implicitamente all'inizio dei cicli.IL next() Il metodo restituisce il valore successivo e viene chiamato implicitamente ad ogni incremento del ciclo. next() solleva un'eccezione StopIteration quando non ci sono più valori da restituire, che viene implicitamente acquisita eseguendo il looping dei costrutti per interrompere l'iterazione.

Ecco un semplice esempio di contatore:

class Counter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def next(self): # Python 3: def __next__(self)
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1


for c in Counter(3, 8):
    print c

Questo stamperà:

3
4
5
6
7
8

Questo è più semplice da scrivere utilizzando 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, 8):
    print c

L'output stampato sarà lo stesso.Dietro le quinte, l'oggetto generatore supporta il protocollo iteratore e fa qualcosa di più o meno simile alla classe Counter.

L'articolo di David Mertz, Iteratori e generatori semplici, è un'ottima introduzione.

Altri suggerimenti

Esistono quattro modi per costruire una funzione iterativa:

Esempi:

# generator
def uc_gen(text):
    for char in text:
        yield char.upper()

# generator expression
def uc_genexp(text):
    return (char.upper() for char in text)

# iterator protocol
class uc_iter():
    def __init__(self, text):
        self.text = text
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = self.text[self.index].upper()
        except IndexError:
            raise StopIteration
        self.index += 1
        return result

# getitem method
class uc_getitem():
    def __init__(self, text):
        self.text = text
    def __getitem__(self, index):
        result = self.text[index].upper()
        return result

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

Il 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 può essere reversed();il semplice iteratore (uc_iter) avrebbe bisogno di __reversed__ metodo magic (che deve restituire un nuovo iteratore che va all'indietro);e il getitem iterabile (uc_getitem) deve avere il __len__ metodo magico:

    # for uc_iter
    def __reversed__(self):
        return reversed(self.text)

    # for uc_getitem
    def __len__(self)
        return len(self.text)

Per rispondere alla domanda secondaria del colonnello Panic su un iteratore infinito valutato pigramente, ecco questi esempi, utilizzando ciascuno dei quattro metodi sopra indicati:

# 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 che si traduce in (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

Innanzitutto il modulo itertools è incredibilmente utile per tutti i tipi di casi in cui un iteratore sarebbe utile, ma ecco tutto ciò che serve per creare un iteratore in Python:

prodotto

Non è bello?La resa può essere utilizzata per sostituire una normale ritorno in una funzione.Restituisce l'oggetto lo stesso, ma invece di distruggere lo stato ed uscire, salva lo stato per quando si desidera eseguire l'iterazione successiva.Eccone un esempio in azione estratto direttamente dal file Elenco delle funzioni di itertools:

def count(n=0):
    while True:
        yield n
        n += 1

Come indicato nella descrizione delle funzioni (è il file contare() funzione dal modulo itertools...), produce un iteratore che restituisce numeri interi consecutivi che iniziano con n.

Espressioni del generatore sono tutta un'altra scatola di vermi (vermi fantastici!).Possono essere utilizzati al posto di a Comprensione delle liste per risparmiare memoria (le comprensioni delle liste creano una lista in memoria che viene distrutta dopo l'uso se non assegnata a una variabile, ma le espressioni del generatore possono creare un oggetto generatore...che è un modo elegante per dire Iterator).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'intervallo completo è predeterminato tra 0 e 10.

Ho appena trovato xrange() (sorpreso di non averlo visto prima...) e l'ho aggiunto all'esempio sopra. xrange() è una versione iterabile di allineare() che ha il vantaggio di non precostruire l'elenco.Sarebbe molto utile se avessi un enorme corpus di dati su cui eseguire l'iterazione e avessi solo tanta memoria per farlo.

Vedo che alcuni di voi lo fanno return self In __iter__.Volevo solo sottolinearlo __iter__ stesso può essere un generatore (eliminando così la necessità di __next__ e innalzamento 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 realizzare direttamente un generatore, ma per classi più complesse può essere utile.

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, cioèdaglielo __getitem__ E __len__ metodi.L'ho testato 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)

Questa è una funzione iterabile senza yield.Si avvale del iter funzione e una chiusura che mantiene il suo stato in un formato mutabile (list) nell'ambito allegato per 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 è mantenuto immutabile nell'ambito di inclusione 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

Tutte le risposte in questa pagina sono davvero ottime per un oggetto complesso.Ma per quelli che contengono tipi iterabili incorporati come attributi, come str, list, set O dict, o qualsiasi implementazione di collections.Iterable, puoi omettere alcune cose nella tua 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 string)

Può essere utilizzato come:

for x in Test("abcde"):
    print(x)

# prints
# a
# b
# c
# d
# e

Se cerchi 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, ecco 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)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top