2009-09-03 14 views
13

Come si crea un generatore di ripetizioni, come xrange, in Python? Per esempio, se lo faccio:Come creare un generatore di ripetizioni in Python

>>> m = xrange(5) 
>>> print list(m) 
>>> print list(m) 

Ottengo lo stesso risultato entrambe le volte - i numeri 0..4. Tuttavia, se provo la stessa cosa con rendimento:

>>> def myxrange(n): 
... i = 0 
... while i < n: 
...  yield i 
...  i += 1 
>>> m = myxrange(5) 
>>> print list(m) 
>>> print list(m) 

La seconda volta che provo a iterare m, non ottengo niente di nuovo - una lista vuota.

C'è un modo semplice per creare un generatore di ripetizioni come xrange con rendimento o comprensione del generatore? Ho trovato a workaround on a Python tracker issue, che utilizza un decoratore per trasformare un generatore in un iteratore. Questo si riavvia ogni volta che lo usi, anche se non hai usato tutti i valori l'ultima volta, proprio come xrange. Ho anche venuto con il mio decoratore, sulla base della stessa idea, che in realtà restituisce un generatore, ma uno che può riavviare dopo aver lanciato un'eccezione StopIteration:

@decorator.decorator 
def eternal(genfunc, *args, **kwargs): 
    class _iterable: 
    iter = None 
    def __iter__(self): return self 
    def next(self, *nargs, **nkwargs): 
     self.iter = self.iter or genfunc(*args, **kwargs): 
     try: 
     return self.iter.next(*nargs, **nkwargs) 
     except StopIteration: 
     self.iter = None 
     raise 
    return _iterable() 

C'è un modo migliore per risolvere il problema, utilizzando solo rendimento e/o comprensione del generatore? O qualcosa di costruito in Python? Quindi non ho bisogno di rotolare le mie classi e decoratori?

Aggiornamento

Il comment by u0b34a0f6ae inchiodato la fonte della mia equivoco:

xrange (5) non restituisce un iteratore, si crea un oggetto xrange. Gli oggetti xrange possono essere ripetuti, proprio come i dizionari, più di una volta.

La mia funzione "eterna" abbaiava contro l'albero sbagliato tutto, agendo come un iteratore/generatore (__iter__ restituisce auto), piuttosto che come una collezione/xrange (__iter__ restituisce un nuovo iteratore).

+2

Piccola nitpick, ma 'xrange()' non è un generatore. 'type (xrange (4))'! = 'type (myxrange (4))'. –

+2

Penso che sia più di un piccolo nitpick. Questa è l'intera ragione della differenza. E come John ha sottolineato, il comportamento desiderato può essere ottenuto con un __iter__ sovraccarico. – ricree

+0

Ho molti problemi a seguire il codice di implementazione, rispetto alle altre due implementazioni proposte (una nel problema del tracker Python a cui ti sei collegato, l'altra nella risposta di @ JohnMillikin). In particolare, avendo difficoltà a capire: (1) cosa significa esattamente "@ decorator.decorator". Puoi dare un link a doc per questo? (2) un esempio di utilizzo sarebbe molto utile; in particolare, uno che esercita args e nargs (3) e puoi dare un esempio di come la tua gestione di StopIteration aggiunge valore? ovvero un esempio in cui la vostra implementazione ha esito positivo, ma le altre due implementazioni falliscono. –

risposta

15

Non direttamente. Parte della flessibilità che consente ai generatori di essere utilizzati per l'implementazione di co-routines, gestione delle risorse, ecc, è che sono sempre one-shot. Una volta eseguito, un generatore non può essere rieseguito. Dovresti creare un nuovo oggetto generatore.

Tuttavia, è possibile creare la propria classe che sostituisce __iter__(). Si agirà come un generatore riutilizzabile:

def multigen(gen_func): 
    class _multigen(object): 
     def __init__(self, *args, **kwargs): 
      self.__args = args 
      self.__kwargs = kwargs 
     def __iter__(self): 
      return gen_func(*self.__args, **self.__kwargs) 
    return _multigen 

@multigen 
def myxrange(n): 
    i = 0 
    while i < n: 
    yield i 
    i += 1 
m = myxrange(5) 
print list(m) 
print list(m) 
+0

Fondamentalmente identico al rimedio che ho collegato alla mia domanda: immagino che l'abbiate perso. Ma grazie! –

+2

"Soluzione alternativa". Non c'è molto da fare. xrange (5) non restituisce un iteratore, crea un oggetto xrange. Gli oggetti xrange possono essere ripetuti, proprio come i dizionari, più di una volta. – u0b34a0f6ae

0

Penso che la risposta sia "No". Forse ho torto. Può essere che con alcune delle cose nuove e divertenti che puoi fare con i generatori in 2.6 che coinvolgono argomenti e gestione delle eccezioni che consentirebbero qualcosa come quello che vuoi. Ma quelle caratteristiche sono principalmente intese per l'implementazione di semi-continuazioni.

Perché non vuoi avere le tue classi o decoratori? E perché volevi creare un decoratore che restituisse un generatore invece di un'istanza di classe?

+0

(1) Perché richiede più codice e sembra qualcosa che potrebbe essere stato implementato in un modo che non conoscevo. (2) Perché inizialmente ho frainteso xrange e ho pensato che volevo un generatore eterno piuttosto che un iterabile. –

1

Se si scrive un sacco di questi, la risposta di John Millikin è il più pulito si arriva.

Ma se non ti dispiace aggiungere 3 righe e qualche rientro, puoi farlo senza un decoratore personalizzato.Questo compone 2 trucchi:

  1. [generalmente utile:] Si può facilmente fare una classe iterabile senza implementare .next() - basta usare un generatore per __iter__(self)!

  2. Invece di preoccuparsi di un costruttore, è possibile definire una classe unica all'interno di una funzione.

=>

def myxrange(n): 
    class Iterable(object): 
     def __iter__(self): 
      i = 0 
      while i < n: 
       yield i 
       i += 1 
    return Iterable() 

Piccola stampa: non ho la prova delle prestazioni, le classi di deposizione delle uova come questo potrebbe essere uno spreco. Ma impressionante ;-)

-1

utilizzare questa soluzione:

>>> myxrange_ = lambda x: myxrange(x) 
>>> print list(myxrange_(5)) 
... [0, 1, 2, 3, 4] 
>>> print list(myxrange_(5)) 
... [0, 1, 2, 3, 4] 

>>> for number in myxrange_(5): 
...  print number 
... 
    0 
    1 
    2 
    3 
    4 
>>> 

e con un decoratore:

>>> def decorator(generator): 
...  return lambda x: generator(x) 
... 
>>> @decorator 
>>> def myxrange(n): 
... i = 0 
... while i < n: 
...  yield i 
...  i += 1 
... 
>>> print list(myxrange(5)) 
... [0, 1, 2, 3, 4] 
>>> print list(myxrange(5)) 
... [0, 1, 2, 3, 4] 
>>> 

semplice.

+0

¿-1 ?, non capisco questo. questa soluzione soddisfa i requisiti della domanda. – SmartElectron

+0

No, perché i parametri necessari per ripetere l'iterazione non sono incapsulati nell'oggetto - devono essere passati ogni volta che si desidera un nuovo riutilizzo. L'intero punto di un oggetto generatore è quello di incapsulare le informazioni necessarie per eseguire l'iterazione. Anche un generatore di resuable dovrebbe farlo.Si noti come la soluzione di John Millikin non richiede di digitare "5" ogni volta che si desidera utilizzare il generatore. Digitate "5" una volta e l'oggetto si ricorda per voi da quel punto in poi. – Paul

+0

Inoltre, la sintassi non è la stessa di un generatore. Sono necessarie parentesi aggiuntive. – Paul

1

Usando itertools è semplicissimo.

import itertools 

alist = [1,2,3] 
repeatingGenerator = itertools.cycle(alist) 

print(next(generatorInstance)) #=> yields 1 
print(next(generatorInstance)) #=> yields 2 
print(next(generatorInstance)) #=> yields 3 
print(next(generatorInstance)) #=> yields 1 again! 
+0

Ciò farebbe sì che 'print list (m)' (un requisito nella domanda) venga eseguito per sempre, tuttavia: / –

0

È possibile ripristinare iteratori con more_itertools.seekable, uno strumento di terze parti.

Installare tramite > pip install more_itertools.

import more_itertools as mit 


def myxrange(n): 
    """Yield integers.""" 
    i = 0 
    while i < n: 
     yield i 
     i += 1 

m = mit.seekable(myxrange(5)) 
print(list(m)) 
m.seek(0)            # reset iterator 
print(list(m)) 
# [0, 1, 2, 3, 4] 
# [0, 1, 2, 3, 4] 

Nota: il consumo di memoria cresce mentre avanza un iteratore, in modo da essere diffidenti avvolgimento grandi iterabili.

Problemi correlati