6.2. Package

Analizzare un documento XML è estremamente semplice: una sola riga di codice. Comunque, prima di studiare questa riga di codice, dobbiamo fare una piccola deviazione per parlare dei package.

Esempio 6.5. Caricare un documento XML (una rapida occhiata)

>>> from xml.dom import minidom 1
>>> xmldoc = minidom.parse('~/diveintopython/common/py/kgp/binary.xml')
1 Questa è una sintassi che non abbiamo visto in precedenza. Assomiglia molto alla from module import che conosciamo ed amiamo, ma il "." la fa sembrare qualche cosa che va oltre una semplice importazione di un modulo. Infatti, xml è quella cosa nota come package, dom è un package annidato in xml e minidom è un modulo all'interno di xml.dom.

Suona complicato, ma in realtà non lo è. Uno sguardo all'implementazione può aiutare. I package sono qualcosa di più che directory di moduli; i package annidati sono sottodirectory. I moduli in un package (o in un package annidato) sono semplicemente dei file .py, come sempre, salvo il fatto che sono in una sottodirectory invece che nella directory principale lib/ della vostra installazione Python.

Esempio 6.6. Disposizione dei file di un package

Python21/           root Python installation (home of the executable)
|
+--lib/             library directory (home of the standard library modules)
   |
   +-- xml/         xml package (really just a directory with other stuff in it)
       |
       +--sax/      xml.sax package (again, just a directory)
       |
       +--dom/      xml.dom package (contains minidom.py)
       |
       +--parsers/  xml.parsers package (used internally)

Quindi, quando diciamo from xml.dom import minidom, Python capisce che questo significa “cerca una directory dom nella directory xml, cerca dentro il modulo minidom ed importalo come minidom”. Ma Python è ancora più intelligente di così; non solo potete importare interi moduli contenuti in un package, potete selettivamente importare classi specifiche o funzioni da un modulo contenuto in un package. Potete anche importare l'intero package come un modulo. La sintassi è la stessa; Python capisce che cosa volete in base alla disposizione dei file nel package ed automaticamente fa la cosa giusta.

Esempio 6.7. I package sono anche dei moduli

>>> from xml.dom import minidom         1
>>> minidom
<module 'xml.dom.minidom' from 'C:\Python21\lib\xml\dom\minidom.pyc'>
>>> minidom.Element
<class xml.dom.minidom.Element at 01095744>
>>> from xml.dom.minidom import Element 2
>>> Element
<class xml.dom.minidom.Element at 01095744>
>>> minidom.Element
<class xml.dom.minidom.Element at 01095744>
>>> from xml import dom                 3
>>> dom
<module 'xml.dom' from 'C:\Python21\lib\xml\dom\__init__.pyc'>
>>> import xml                          4
>>> xml
<module 'xml' from 'C:\Python21\lib\xml\__init__.pyc'>
1 Qui stiamo importando un modulo (minidom) da un package annidato (xml.dom). Il risultato è che minidom è importato nel nostro namespace e per ottenere un riferimento alle classi interne al modulo minidom (come Element), dobbiamo anteporre al loro nome il nome del modulo.
2 Qui stiamo importando una classe (Element) da un modulo (minidom) contenuto in un package annidato (xml.dom). Il risultato è che Element è importata direttamente nel nostro namespace. Notate che questo non interferisce con l'import precedente; ora si può fare riferimento alla classe Element in due modi, ma è sempre la medesima classe.
3 Qui stiamo importando il package dom (un package annidato di xml) come se fosse un modulo a sé stante. Ogni livello di un package può essere trattato come un modulo, come vedremo tra breve. Può anche avere i propri attributi e metodi, come i moduli che abbiamo già visto prima.
4 Qui stiamo importando come un modulo il livello radice del package xml.

Come può dunque un package (che è semplicemente una directory nel disco) essere importato e trattato come un modulo (che è sempre un file in un disco)? La risposta sta nella magia del file __init__.py. Vedete, i package non sono semplicemente directory, sono directory con un file specifico, __init__.py, al loro interno. Tale file definisce gli attributi ed i metodi del package. Per esempio, xml.dom contiene una classe Node che è definita in xml/dom/__init__.py. Quando importate un package come un modulo (come dom da xml), state in realtà importando il suo file __init__.py.

Nota
Un package è una directory con il file speciale __init__.py dentro. Il file __init__.py definisce gli attributi ed i metodi del package. Non deve obbligatoriamente definire nulla; può essere un file vuoto, ma deve esistere. Ma se __init__.py non esiste, la directory è soltanto una directory, non un package e non può essere importata, contenere moduli o package annidati.

Allora perché preoccuparsi dei package? Beh, mettono a disposizione un modo per ragruppare logicamente moduli tra loro correlati. Invece di avere un package xml con i package sax e dom al suo interno, gli autori avrebbero dovuto scegliere di inserire tutte le funzionalità sax in xmlsax.py e tutte le funzionalità dom in xmldom.py, oppure metterle tutte quante in un solo modulo. Ma questo sarebbe stato molto ingombrante (al momento della scrittura, il package XML conta più di 3000 righe di codice) e difficile da gestire (sorgenti separati significa che più persone possono lavorare contemporaneamente su aree diverse).

Se vi ritrovate a scrivere un vasto sottosistema in Python (o più probabilmente, quando realizzate che il vostro piccolo sottosistema è diventato grande), investite un po' di tempo nel disegnare una buona architettura del package. È una delle molte cose in cui Python eccelle, quindi avvantaggiatevene.