I generatori di Python nell’uso quotidiano

Pubblicato: 30 novembre 2009 in Python
Tag:

Quando ho cominciato ad affrontare l’affascinante questione dei generatori in Python avevo il dubbio che la vita non mi avrebbe mai offerto la possibilità di utilizzarli realmente, mi sembrava un tema talmente astratto da necessitare, per forza, di grandi progetti e di grandi sforzi.
Questo fino ad oggi.
Oggi invece ho capito che i generatori si possono usare anche per risolvere problemi spiccioli e danno grandi soddisfazioni (estetiche, se non altro).

Cosa siano di preciso i generatori esula un po’, in fondo metto un paio di link fondamentali a pagine in cui si tratta diffusamente della questione, in brevissimo però va almeno detto che i generatori sono un meccanismo che serve alla creazione di iteratori conformi allo standard, che possono quindi essere percorsi in modo del tutto naturale con cicli di for, e che i generatori si basano su una keyword introdotta nello standard da pochi mesi: yield.

Quando facciamo un normale ciclo di for:

for x in [4,8,15,16,23,42]:
    print x,

di fatto il protocollo di iterazione che sta sotto compie più o meno le seguenti operazioni:

>>> a = [4,8,15,16,23,42]
>>> x = iter(a)
>>> x.next()
4
>>> x.next()
8
# ... eccetera
>>> x.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Cioè in pratica:

_iter = iter([4,8,15,16,23,42])
while 1:
    try:
        x = _iter.next()
    except StopIteration:
        break

E questo è proprio il protocollo di cui parlavo prima, che se uno ha voglia di sbattersi può provare ad implementare più o meno così:

class countdown(object):
    def __init__(self,start):
        self.count = start
    def __iter__(self):
        return self
    def next(self):
        if self.count <= 0:
            raise StopIteration
        r = self.count
        self.count -= 1
        return r

>>> c = countdown(5)
>>> for i in c:
...    print i,
...
5 4 3 2 1
>>>

Ebbene, guardate che bello che viene usando i generatori:

def countdown(n):
    while n > 0:
        yield n # <---- LA PIETRA DELLO SCANDALO
        n -= 1

>>> for i in countdown(5):
...    print i,
...
5 4 3 2 1
>>>

All’istanziazione della funzione il generatore viene creato ma nessun codice viene eseguito, l’esecuzione si compie solamente quando viene incontrata un’istruzione next(), yield rilascia un valore e poi ferma di nuovo tutto in attesa del successivo next(). Quando il generatore, che è anche una funzione, ritorna, l’iterazione si interrompe.

>>> x = countdown(10)
>>> x
<generator object at 0xb7d7cbcc>
>>> x.next()
10
>>> x.next()
9
# ... eccetera
>>> x.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Tutto molto bello, ma come usarlo in pratica? Veniamo ad oggi.

Il problema era questo: siamo su un Linux che fa da file server per una rete mista ed alcuni applicativi generano files di scambio con certi nomi prefissati. Solo che tutti questi applicativi girano sotto Windows, che non distingue la destra dalla sinistra maiuscole e minuscole, questi applicativi hanno anche storie di manutenzioni e default diversi per cui, abbiamo scoperto, può succedere che esistano multiple copie di quello che dovrebbe essere un solo file, a causa proprio del maiuscolo/minuscolo.

Il codice seguente intercetta tutte le copie di un certo file ‘pippo.txt’ a meno delle maiuscole/minuscole:

import os

def cerca_file_ci(filename, percorso):
    filename = filename.upper()
    for path, dirs, files in os.walk(percorso):
        for f in files:
            if f.upper() == filename:
                yield f

path = '/usr/local/quelchevvuoi'
tuttelecopie = [a for a in cerca_file_ci('pippo.txt', path)]

Nel senso che nella lista ‘tuttelecopie’ ci finiscono, beh, tutte le sue copie.

Riferimenti:

PEP 255 — Simple Generators
Generator Tricks for Systems Programmers (che ancora un po’ traducevo tutto)

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...