4.15. Mettere tutto insieme

Ancora una volta tutte le nostre pedine del domino sono al loro posto. Abbiamo visto come funziona ogni linea di codice, adesso facciamo un passo indietro e vediamo come il tutto venga messo insieme.

Esempio 4.39. listDirectory


def listDirectory(directory, fileExtList):                                         1
    "get list of file info objects for files of particular extensions"
    fileList = [os.path.normcase(f) for f in os.listdir(directory)]               
    fileList = [os.path.join(directory, f) for f in fileList \
                if os.path.splitext(f)[1] in fileExtList]                          2
    def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):       3
        "get file info class from filename extension"                             
        subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:]        4
        return hasattr(module, subclass) and getattr(module, subclass) or FileInfo 5
    return [getFileInfoClass(f)(f) for f in fileList]                              6
1 listDirectory è la pricipale attrazione dell'intero modulo. Prende una directory (come c:\music\_singles\, nel mio caso) ed una lista di interessanti estensioni di file (come ['.mp3']) e ritorna una lista di istanze di classe che funzionano come un dizionario. E lo fa in poche schiette linee di codice.
2 Come abbiamo visto nella precedente sezione, questa linea di codice prende una lista dei pathname di tutti i file in directory che hanno un'estensione interessante (come specificato da fileExtList).
3 Ai programmatori Pascal della vecchia scuola potranno essere familiari, ma molte persone hanno sgranato gli occhi quando gli ho detto che Python supporta le funzioni annidate -- letteralmente, una funzione dentro un'altra funzione. La funzione annidata getFileInfoClass può essere chiamata solo dalla funzione nella quale è definita, listDirectory. Come con ogni altra funzione, non avete bisogno di dichiarazione d'interfaccia od altre cose strane; limitatevi a definire la funzione e scriverla.
4 Adesso che avete visto il modulo os, questa linea dovrebbe avere più significato. Di fatto, prende l'estensione del file (os.path.splitext(filename)[1]), la forza a caratteri maiuscoli (.upper()), affetta via il punto ([1:]) e costruisce un nome di classe con la formattazione di stringa. Quindi c:\music\ap\mahadeva.mp3 diventa .mp3 che diventa .MP3 che diventa MP3 che diventa MP3FileInfo.
5 Avendo costruito il nome del gestore di classe che gestirebbe questo file, controlliamo se quel gestore di classe realmente esiste in questo modulo. Se lo fa, ritorniamo la classe, altrimenti ritorniamo la classe base FileInfo. Questo è un punto molto importante: questa funzione ritorna una classe. Non un'istanza di una classe, ma la classe stessa.
6 Per ogni file nella nostra lista “file interessanti” (fileList), possiamo chiamare getFileInfoClass con il filename (f). Chiamando getFileInfoClass(f) viene ritornata una classe; noi non sappiamo esattamente quale, ma non ci importa. Creiamo poi un'istanza di questa classe (qualunque essa sia) e passiamo il nome del file (ancora f), al metodo di __init__. Come abbiamo visto prima in questo capitolo, il metodo __init__ di FileInfo imposta self["name"], che richiama __setitem__, che viene sovrascritto nella classe discendente (MP3FileInfo), in modo da analizzare correttamente il file, per ottenerne il metadato. Facciamo tutto ciò per ogni file interessante e ritorniamo una lista delle istanze risultanti.

Notate che listDirectory è assolutamente generica; non conosce anticipatamente quale tipo di file otterrà o quali classi tra quelle definite potrebbero potenzialmente gestire questi file. Essa analizza la directory, alla ricerca dei file da processare e poi introspetta il suo stesso modulo per vedere quali speciali gestori di classi (come MP3FileInfo) sono definiti. Potete estendere questo programma per gestire altri tipi di file, semplicemente definendo una classe col nome appropriato: HTMLFileInfo per i file HTML, DOCFileInfo per i file .doc di Word e così via. listDirectory li gestirà tutti, senza modifiche, delegando il lavoro effettivo alle classi appropriate e collazionando i risultati.