[Python] istruzione yield

Marco Giusti marco.giusti a gmail.com
Ven 18 Maggio 2012 14:19:33 CEST


On Fri, May 18, 2012 at 01:19:11PM +0200, paride_900 a libero.it wrote:
> Salve ragazzi
> 
> navigando un po in rete mi sono imbattuto in questo pezzo di codice Python:
> 
> 
> def splitlist(lista,lunghezza):
>     i=0
>     while 1:
>         fine=i+lunghezza
>         if len(lista)<=i: return
> 
>         yield lista[i:fine]
>         i=fine
>         
> print list(splitlist([1,2,3,4],2))
> 
> Vi spiego fin dove sono riuscito a capirlo io.
> 
> Si crea la funzione 'splitlist' con i parametri 'lista' e 'lunghezza'
> Si crea la variabile 'i' e gli viene assegnato il valore zero.
> Si crea un ciclo while 1, ovvero fino a che la condizione sarà 1.
> Si crea la variabile 'fine' e gli vengono assegnati le variabili 'i' e 
> 'lunghezza' da sommare, quindi a questo punto abbiamo la variabile 'fine' che 
> vale due.
> Quindi si passa all'istruzione 'if' che verifica se gli elementi della lista 
> sono minori o uguali a 'i', cioè a zero. Se è vero che sono minori o uguali 
> ritornerà la lista vuota.
> 
> Dall'istruzione sotto in poi non riesco a capirci più nulla.
> In particolare non ho capito il funzionamento dell'istruzione yield, da quel 
> che ho letto in rete si tratta di un generatore o una cosa del genere ma non ho 
> capito bene come agisce.

detto in maniera rozza un generatore è un oggetto che deve effettuare
una certa computazione ma non la fa' tutta in una sola volta ma procede
per passi, ritornando il controllo al chiamante ogni volta che incontra
l'istruzione yield.

un generatore è una funzione che ha il suo interno uno statement yield
quindi questo in realtà non ti aiuta molto a capire se non sai come
funziona la yield. un esempio più semplice:

	>>> def uno():
	...  print('io sono il generatore uno')
	...  while 1:
	...   print('uno')
	...   yield 1
	... 
	>>> g = uno()
	>>> next(g)
	io sono il generatore uno
	uno
	1
	>>> next(g)
	uno
	1
	>>> a = next(g)
	uno
	>>> a
	1
	>>> 

appena invochi la funzione `uno` noti la prima differenza con una
funzione normale, le normali istruzioni che ti aspetteresti che vengano
eseguite in realtà non lo sono ma viene instanziato un nuovo oggetto di
tipo generator.

	>>> type(g)
	<type 'generator'>
	>>> g
	<generator object uno at 0x7fe435f0a9b0>
	>>> 

solo con la prima `next` le istruzioni vengono eseguite, quindi incontri
la prima `print`, entri nel ciclo, incontri la seconda `print` e poi la
yield. questa è una specie di return, e di fatti ritorna `1`, ma lo
stato interno della funzione non va' perso ma solo sospeso. con la
seconda `next`, il generatore riprende dall'ultima istruzione eseguita e
di fatti è sempre dentro il ciclo e solo la seconda `print` viene
eseguita e non la prima. Il fatto che lo stato del generatore non va'
perso lo vedi bene nel secondo esempio:

	>>> def count(start=0):
	...  while 1:
	...   yield start
	...   start += 1
	... 
	>>> c = count()
	>>> next(c)
	0
	>>> next(c)
	1
	>>> next(c)
	2

come vedi la variabile `start` non viene inizializata ad ogni chiamata
di `next` ma solo la prima volta ed il suo stato è persistente tra una
`next` e l'altra. qui è esplicito come la chiamata a `count` crei due
oggetti differenti ognuno con un suo stato, quindi `start` non la devi
pensare come una variabile static del C.

	>>> c2 = count()
	>>> next(c2)
	0
	>>> next(c2)
	1
	>>> next(c)
	3
	>>> 

ora gli iteratori funzionano bene con i cicli for:

	>>> def count(start=0, step=1, stop=-1):
	...  while start != stop:
	...   yield start
	...   start += step
	... 
	>>> c = count(stop=3)
	>>> for i in c: print(i, end=' ')
	... 
	0 1 2 >>> for i in c: print(i, end=' ') # i gen. sono usa e getta
	... 
	>>> for i in count(stop=5): print(i, end=' ')
	... 
	0 1 2 3 4 >>> 

comunque la documentazione è ampissima, quindi ti rimando al tutorial
perché con i generatori, e iteratori, puoi fare molto di più.

http://docs.python.org/tutorial/classes.html#generators

m.


Maggiori informazioni sulla lista Python