5.10. Mettere tutto insieme

È tempo di mettere a frutto tutto quello che abbiamo imparato finora. Spero che abbiate prestato attenzione.

Esempio 5.21. La funzione translate, parte prima


def translate(url, dialectName="chef"): 1
    import urllib                       2
    sock = urllib.urlopen(url)          3
    htmlSource = sock.read()           
    sock.close()                       
1 La funzione translate ha un argomento opzionale dialectName, che è una stringa indicante il dialetto da usare. Fra poco vedremo come viene usato questo parametro.
2 Ehi, aspettate un secondo, c'è una istruzione import in questa funzione! Questo è perfettamente lecito in Python. Siete abituati a vedere istruzioni import all'inizio di un programma, cosa che implica che il modulo importato è disponibile in qualsiasi punto del programma. Ma è anche possibile importare un modulo in una funzione. Se si ha un modulo che è usato solo all'interno di una funzione, questa è una maniera semplice di rendere il vostro codice più modulare. (Quando scoprirete che il vostro esercizio di un fine settimana è diventato un complesso lavoro di 800 linee e deciderete di dividerlo in una dozzina di moduli riusabili, apprezerete questa finezza.)
3 Ora si prende il sorgente dalla URL specificata.

Esempio 5.22. La funzione translate, parte seconda: sempre più curioso

    parserName = "%sDialectizer" % dialectName.capitalize() 1
    parserClass = globals()[parserName]                     2
    parser = parserClass()                                  3
1 capitalize è un metodo del tipo stringa che non abbiamo mai incontrato finora; semplicemente, questo metodo rende maiuscola la prima lettera di una stringa e minuscole tutte le altre. Combinandolo con un po' di formattazione di stringhe, si riesce a prendere il nome di un dialetto ed a trasformarlo nel nome della classe “Dialectizer” corrispondente. Se dialectName è uguale alla stringa 'chef', allora parserName sarà uguale alla stringa 'ChefDialectizer'.
2 Abbiamo il nome di una classe in forma di stringa (parserName) e abbiamo lo spazio dei nomi globale in forma di dizionario (globals()). Combinati, otteniamo un riferimento alla classe nominata dalla stringa. (Ricordate, le classi sono oggetti, e possono essere assegnati a variabili proprio come ogni altro oggetto.) Se parserName è uguale alla stringa 'ChefDialectizer', allora parserClass sarà uguale alla classe ChefDialectizer.
3 Alla fine, ecco che abbiamo l'oggetto classe (parserClass), e ci serve una instanza di tale classe. Bene, sappiamo già come fare: basta chiamare la classe come fosse una funzione. Il fatto che la classe è memorizzata in una variabile locale non fa assolutamente alcuna differenza; chiamiamo semplicemente la variabile locale come una funzione e vien fuori un'istanza della classe. Se parserClass è uguale alla classe ChefDialectizer, allora parser sarà uguale ad un instanza della classe ChefDialectizer.

Ma perché darsi tanta pena? Dopo tutto, ci sono solo tre classi Dialectizer; perché non usare semplicemente una istruzione case? (Beh, non c'è l'istruzione case in Python, ma perché non usare una serie di istruzioni if ?) Una ragione c'è: estensibilità. La funzione translate non dipende in nessun modo dal numero di classi “Dialectizer” che sono state definite. Immaginate che domani si definisca una nuova classe FooDialectizer; la funzione translate funzionerebbe semplicemente passando 'foo' come valore del parametro dialectName.

Ancora meglio, immaginatevi di mettere FooDialectizer in un modulo separato, e di importare tale modulo con la sintassi from module import. Abbiamo già visto come questo includa il modulo nello spazio dei nomi globali restituito da globals(), per cui translate funzionerebbe ancora senza bisogno di modifiche, sebbene FooDialectizer sia definita in un file separato.

Ora immaginate che il nome del dialetto venga da qualche luogo esterno al programma, magari da un database o da un valore inserito dall'utente in una form. È possibile usare una qualsiasi delle molte architetture di scripting server-side in Python per la generazione dinamica di pagine HTML; questa funzione potrebbe accettare una URL ed il nome di un dialetto (entrambi stringhe) nella stringa di query di una richiesta di pagina web, e restituire come output la pagina web “tradotta”.

Infine, immaginate una infrastruttura Dialectizer con una architettura che faccia uso di plug-in. Potreste mettere ogni classe Dialectizer in un file diverso, lasciando solo la funzione translate nel modulo dialect.py. Assumendo una metodologia consistente nell'assegnare i nomi, la funzione translate potrebbe importare dinamicamente la classe richiesta dal file corrispondente, partendo da nient'altro che il nome del dialetto. (Non abbiamo ancora visto esempi di import dinamico, ma vi prometto di trattare questo argomento in uno dei prossimi capitoli.) Per aggiungere un nuovo dialetto, potreste semplicemente aggiungere un file con il nome appropriato nella directory dei plug-in (ad esempio un file foodialect.py che contenga la classe FooDialectizer). Chiamando la funzione translate usando 'foo' come nome del dialetto provocherebbe il ritrovamento del modulo foodialect.py, l'import della classe FooDialectizer, e così via.

Esempio 5.23. La funzione translate, parte terza

    parser.feed(htmlSource) 1
    parser.close()          2
    return parser.output()  3
1 Dopo tutto quell' immaginare, questo vi potrà sembrare piuttosto noioso, ma è la funzione feed che inizializza l'intera trasformazione. L'intero sorgente HTML è memorizzato in una stringa, per cui basta chiamare feed solo una volta. Tuttavia, è anche possibile chiamare feed quante volte si vuole e il parser continuerà semplicemente ad analizzare. Se vi state preoccupando di usare troppa memoria ( o sapete di dover gestire pagine HTML molto grandi), è possibile creare un ciclo in cui si leggano pochi bytes di HTML per volta da passare al parser. Il risultato sarebbe identico.
2 Poiché feed mantiene un buffer interno, è opportuno chiamare sempre il metodo close del parser una volta finito (anche se si passa tutto il codice HTML in un colpo solo, come abbiamo fatto noi). Altrimenti, potreste scoprire che al vostro output mancano gli ultimi bytes.
3 Ricordate, output è la funzione che abbiamo definito nella classe BaseHTMLProcessor per mettere insieme tutte le parti dell'output che avevamo memorizzato e restituirle in una singola stringa.

Ed ecco fatto, abbiamo “tradotto” una pagina web, passando nient'altro che una URL ed il nome di un dialetto.

Ulteriori letture