6. Moduli

Se chiudete l’interprete di Python e poi vi rientrate, non ritroverete le definizioni che avevate impostato (funzioni e variabili). Di conseguenza, se volete scrivere un programma più lungo, vi conviene usare un editor di testo per preparare le istruzioni per l’interprete, e invocarlo poi con il file risultante come input. In questo modo avete creato uno script. Quando poi il vostro programma diventa più lungo, potreste volerlo dividere in diversi file più maneggevoli. Potreste anche voler usare in diversi programmi le stesse funzioni utili che avete scritto, senza bisogno di copiarle tutte le volte.

A questo scopo, in Python potete mettere le definizioni in un file e usarle poi in uno script o nella sessione interattiva dell’interprete. Un file di questo tipo è un modulo; le definizioni di un modulo possono essere importate in altri moduli o nel modulo principale (ovvero, l’insieme delle variabili a cui avete accesso da uno script quando è eseguito, o dalla modalità interattiva).

Un modulo è un file che contiene definizioni e istruzioni Python. Il nome del file è quello del modulo più il suffisso .py. Dentro il modulo, il nome è disponibile come valore della variabile globale __name__ (una stringa). Per esempio, usate il vostro editor preferito per creare un file dal nome fibo.py nella directory corrente, che contiene questo:

# modulo per i numeri di Finonacci

def fib(n):    # scrive i numeri di Fibonacci fino a n
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib2(n):   # restituisce i numeri di Fibonacci fino a n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

Adesso entrate nell’interprete interattivo dei comandi e importate questo modulo così:

>>> import fibo

Questa istruzione non inserisce i nomi delle funzioni definite in fibo direttamente nella tabella dei simboli corrente; invece, vi inserisce il nome del modulo fibo. Usando il nome del modulo potete accedere alle funzioni che contiene:

>>> fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'

Se intendete usare spesso una funzione, potete assegnarle un nome locale:

>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

6.1. Approfondimenti sui moduli

Un modulo può contenere istruzioni eseguibili oltre a definizioni di funzioni. Queste istruzioni devono essere intese come un modo di inizializzare il modulo. Sono eseguite solo la prima volta che il nome del modulo è incontrato in una istruzione import. 1 (Sono anche eseguite se il modulo è eseguito come script.)

Ciascun modulo ha una sua tabella dei simboli, che vale come tabella globale per tutte le funzioni che vi sono definite. Quindi l’autore del modulo può usare delle variabili globali senza preoccuparsi di conflitti accidentali con le variabili globali dell’utente del modulo. D’altra parte, se siete sicuri di quello che fate, potete accedere alle variabili globali del modulo con la stessa notazione che usate per riferirvi alle sue funzioni, ovvero modname.itemname.

I moduli possono importare altri moduli. È consuetudine, ma non obbligatorio, mettere tutte le istruzioni import all’inizio del modulo (o dello script). I nomi dei moduli importati sono inseriti nella tabella dei simboli globale del modulo importatore.

Esiste una variante dell’istruzione import che consente di importare direttamente i nomi contenuti in un modulo nella tabella dei simboli del modulo importatore. Per esempio:

>>> from fibo import fib, fib2
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

In questo modo però il nome del modulo, i cui nomi interni sono importati, non è importato esso stesso (quindi, in questo esempio, fibo non è definito).

C’è poi una variante che consente di importare tutti i nomi definiti in un modulo:

>>> from fibo import *
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

In questo modo vengono importati tutti i nomi del modulo, eccetto quelli che iniziano con un underscore (_). In genere i programmatori Python non usano questa tecnica, dal momento che introduce un numero sconosciuto di nomi nell’interprete, eventualmente sovrascrivendo nomi che erano già stati definiti.

Si noti che in generale importare * da un modulo o da un package è considerato cattiva pratica, perché spesso rende il codice più difficile da leggere. Tuttavia va bene usare questa tecnica nelle sessioni interattive, per risparmiare battute nei nomi da inserire.

Se il nome del modulo è seguito dalla parola-chiave as, allora il nome che segue as è collegato direttamente al modulo importato.

>>> import fibo as fib
>>> fib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Questo modo di importare il modulo è del tutto equivalente a import fibo, con l’unica differenza che adesso il modulo sarà disponibile con il nome fib.

Si può anche usare in combinazione con la parola-chiave from, con effetti analoghi:

>>> from fibo import fib as fibonacci
>>> fibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

Nota

Per ragioni di efficienza, ogni modulo è importato solo una volta nella sessione dell’interprete. Di conseguenza, se nel frattempo modificate il vostro modulo, dovete riavviare l’interprete. In alternativa, se si tratta di un modulo che state testando interattivamente, potete usare la funzione importlib.reload(), ovvero scrivere import importlib; importlib.reload(modulename).

6.1.1. Eseguire moduli come script

Quando eseguite un modulo Python con

python fibo.py <arguments>

il codice del modulo verrà eseguito, proprio come se lo aveste importato, ma la variabile __name__ sarà impostata a "__main__". Ciò vuol dire che, se inserite alla fine del modulo questa clausola:

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

potete rendere questo file utilizzabile sia come script sia come modulo importabile, perché il codice incluso nella clausola, che parsa la riga di comando, verrà eseguito solo quando il modulo è eseguito come il file «main»:

$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34

Se il modulo è importato, il codice non verrà eseguito:

>>> import fibo
>>>

Questa tecnica è usata spesso, sia per fornire una comoda interfaccia utente per un modulo, sia per eseguire dei test (facendo in modo che, quando si esegue il modulo come script, si esegua una suite di test).

6.1.2. Il percorso di ricerca dei moduli

Quando importiamo un modulo di nome spam, l’interprete per prima cosa cerca tra i moduli predefiniti se ne esiste uno con quel nome. Se non lo trova, cerca un file spam.py in una lista di directory contenuta nella variabile sys.path. Questa, a sua volta, viene inizializzata con le seguenti path:

  • La directory che contiene lo script importatore (o la directory corrente se questo non è specificato).

  • La variabile d’ambiente PYTHONPATH (se impostata, contiene una lista di directory, con la stessa sintassi della variabile PATH).

  • Un default che dipende dall’installazione di Python.

Nota

Nei file system che supportano i symlink, la directory che contiene lo script importatore è calcolata dopo aver seguito i symlink. In altre parole, la directory che contiene il symlink non è aggiunta al percorso di ricerca dei moduli.

Dopo che è stata inizializzata, è possibile modificare sys.path dall’interno di un programma Python. La directory che contiene lo script in esecuzione è collocata all’inizio del lista dei percorsi da cercare, davanti alla directory della libreria standard. Ciò vuol dire che i moduli locali, se hanno lo stesso nome, verranno importati al posto di quelli della libreria standard. In genere questo è un errore, a meno che non sia fatto intenzionalmente. Si veda la sezione Moduli della libreria standard per maggiori informazioni.

6.1.3. File «compilati»

Per velocizzare il caricamento dei moduli, Python conserva nella directory di cache __pycache__ una versione compilata di ciascun modulo, con il nome module.version.pyc, dove version specifica il formato del file compilato: di solito è il numero di versione di Python. Per esempio, in CPyhton 3.3 la versione compilata del modulo spam.py si chiamerebbe __pycache__/spam.cpython-33.pyc. Questa convenzione permette la coesistenza di moduli compilati da diverse versioni di Python.

Python confronta la data di ultima modifica del modulo con la sua versione compilata, e ricompila all’occorrenza. Questo processo è completamente automatico. Inoltre, i moduli compilati sono indipendenti dalla piattaforma, così che lo stesso modulo possa essere condiviso su sistemi diversi, con diverse architetture.

Python non controlla la cache in due circostanze. In primo luogo, quando un modulo è caricato direttamente dalla riga di comando, Python ricompila sempre il modulo senza conservarlo nella cache. In secondo luogo, non controlla la cache se non c’è anche il modulo originale. Per ottenere una distribuzione senza sorgenti (solo compilata), oltre a togliere il modulo originale, il modulo compilato deve essere collocato nella directory dei file originali.

Alcuni consigli per gli esperti:

  • Potete usare le opzioni -O o -OO della riga di comando Python per ridurre le dimensioni del modulo compilato. La -O rimuove le istruzioni assert, mentre -OO rimuove sia gli assert sia le docstring. Dal momento che alcuni programmi potrebbero averne bisogno, usate queste opzioni solo se sapete che cosa state facendo. I moduli «ottimizzati» hanno un contrassegno opt- e di solito sono più piccoli. Future versioni di Python potrebbero cambiare gli effetti dell’ottimizzazione.

  • Un programma non è più veloce se usa i file .pyc invece dei normali .py. L’unica differenza è la velocità di caricamento del modulo.

  • Il modulo compileall della libreria standard può compilare tutti i moduli di una directory.

  • Si veda la PEP 3147 per ulteriori dettagli su questi procedimenti, incluso un diagramma di flusso dei vari passaggi.

6.2. Moduli della libreria standard

Python è distribuito con una libreria standard di moduli, documentata in un una sezione separata, la Guida di Riferimento della Libreria Standard. Alcuni moduli sono pre-caricati nell’interprete: questi forniscono delle operazioni che non fanno parte del linguaggio, ma sono comunque predefinite, sia per ragioni di efficienza, sia per dare accesso alle primitive del sistema operativo sottostante. La composizione di questi moduli dipende dalla configurazione, che a sua volta dipende dalla piattaforma. Per esempio, winreg è solo disponibile su Windows. Un modulo meritevole di attenzione particolare è sys, sempre disponibile. Le variabili sys.ps1 e sys.ps2 definiscono le stringhe usate per il prompt primario e secondario:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

Queste variabili sono disponibili solo se l’interprete è in modalità interattiva.

La variabile sys.path è una lista di stringhe che determina il percorso di ricerca dei moduli da importare. È inizializzata con delle path contenute nella variabile d’ambiente PYTHONPATH, oppure da default predefiniti se questa non è impostata. Potete modificare sys.path con le normali tecniche di manipolazione delle liste:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

6.3. La funzione dir()

La funzione predefinita dir() ci dice quali nomi sono definiti in un modulo. Restituisce una lista ordinata di stringhe:

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)  
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__',
 '__package__', '__stderr__', '__stdin__', '__stdout__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe',
 '_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv',
 'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder',
 'call_tracing', 'callstats', 'copyright', 'displayhook',
 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix',
 'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path',
 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
 'thread_info', 'version', 'version_info', 'warnoptions']

Senza argomenti, dir() elenca i nomi disponibili attualmente:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

Si noti che nell’elenco compaiono tutti i tipi di nomi: variabili, moduli, funzioni e così via.

dir() non elenca però i nomi delle funzioni e delle variabili predefinite. Se volete un lista di questi, sono definiti nel modulo builtins:

>>> import builtins
>>> dir(builtins)  
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException',
 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning',
 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError',
 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning',
 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
 'FileExistsError', 'FileNotFoundError', 'FloatingPointError',
 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError',
 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError',
 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented',
 'NotImplementedError', 'OSError', 'OverflowError',
 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError',
 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning',
 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError',
 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError',
 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning',
 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__',
 '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs',
 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits',
 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit',
 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr',
 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass',
 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview',
 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property',
 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars',
 'zip']

6.4. Package

I package sono un modo di strutturare il namespace di un modulo Python usando la «notazione col punto». Per esempio, il nome A.B indica un sotto-modulo B all’interno di un package A. Proprio come i moduli permettono a diversi autori di non doversi preoccupare dei nomi di variabile usati in altri moduli, così i package permettono agli autori di package con molti moduli, come NumPy o Pillow, di non doversi preoccupare dei nomi dei moduli usati da altri.

Immaginate di voler costruire una collezione di moduli (un package) per la gestione di suoni e file sonori. Ci sono diversi formati di file sonori (di solito sono riconoscibili dalle estensioni, per esempio .wav, .aiff, .au): quindi avrete bisogno di creare e mantenere una raccolta crescente di moduli per la conversione tra i vari formati. Ci sono poi molte diverse operazioni che si possono fare sui suoni (mixare, aggiungere eco, equalizzare, creare un effetto stereo artificiale): quindi dovrete scrivere una serie interminabile di moduli che implementano queste operazioni. Ecco una possibile struttura per il vostro package (espressa in forma di gerarchia del file system):

sound/                          package top-level
      __init__.py               inizializzazione del package
      formats/                  sotto-package per le conversioni
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  sotto-package per gli effetti
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  sotto-package per i filtri
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

Quando importate il package, Python cerca nei percorsi della sys.path la directory del package.

I file __init__.py sono necessari perché Python consideri effettivamente come un package la directory che contiene i moduli. Questo è per evitare che directory con un nome comune, per esempio string, possano nascondere inavvertitamente dei nomi di moduli che vengono dopo nell’ordine dei percorsi di ricerca. Nel caso più semplice, __init__.py può essere lasciato vuoto, ma è anche possibile fargli eseguire del codice di inizializzazione o impostare la variabile __all__, come vedremo tra poco.

Gli utenti del package possono importare dei singoli moduli al suo interno, per esempio:

import sound.effects.echo

Questo carica il modulo sound.effects.echo. Occorre riferirsi a questo con il nome completo.

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

Un modo alternativo per importare il modulo è questo:

from sound.effects import echo

Anche in questo modo carichiamo il modulo echo, ma lo rendiamo disponibile senza il prefisso del nome del package. Può essere quindi usato così:

echo.echofilter(input, output, delay=0.7, atten=4)

Un altro modo ancora è importare direttamente la funzione o la variabile richiesta:

from sound.effects.echo import echofilter

Ancora una volta, questo carica il modulo echo, rendendo però direttamente disponibile la sua funzione echofilter():

echofilter(input, output, delay=0.7, atten=4)

Notate che quando si usa la modalità from package import item, allora item può essere sia il nome di un modulo (o sotto-package) del package, sia qualche altro nome definito nel package, come una funzione, una classe o una variabile. L’istruzione import per prima cosa controlla se item è definito nel package; se no, assume che si tratti di un modulo e cerca di caricarlo. Se l’operazione fallisce, viene emessa un’eccezione ImportError.

Al contrario, quando usate la sintassi import item.subitem.subsubitem, ogni elemento eccetto l’ultimo deve essere un package; l’ultimo può essere un package o un modulo, ma non può essere una classe o una funzione o una variabile definita nell’elemento precedente.

6.4.1. Importare * da un package

Che cosa succede quando scriviamo from sound.effects import *? Idealmente, ci si potrebbe aspettare che questa istruzione provochi una scansione nel file system, trovi i moduli presenti nel package e li importi tutti in un colpo solo. Questo però potrebbe richiedere molto tempo e importare un sotto-modulo potrebbe causare side-effect indesiderati, che dovrebbero verificarsi solo quando il modulo è importato direttamente.

L’unica soluzione è che l’autore del package fornisca un indice esplicito del suo contenuto. L’istruzione import segue questa convenzione: se il modulo __init__.py di un package definisce una lista col nome __all__, allora considera questa come l’indice dei moduli che dovrebbero essere importati da un from package import *. È compito dell’autore aggiornare la lista quando rilascia una nuova versione del package. Un autore potrebbe anche non fornire la lista, se decide che non può essere utile importare «*» dal suo package. Per esempio, il file sound/effects/__init__.py potrebbe contenere questo codice:

__all__ = ["echo", "surround", "reverse"]

In questo modo, from sound.effects import * importerebbe i tre moduli indicati del package sound.

Se __all__ non è definito, allora l’istruzione from sound.effects import * non importa comunque tutti i moduli del package sound.effects nel namespace corrente. Si limita a garantire che il package sound.effects sia stato effettivamente importato (eventualmente eseguendo il codice trovato nel file __init__.py) e quindi importa tutti i nomi definiti nel package: questo comprende tutti i nomi definiti (e i moduli esplicitamente importati) nel __init__.py. Include anche tutti i moduli del package che sono stati esplicitamente importati in precedenza. Si consideri questo codice:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

In questo esempio, i moduli echo e surround sono importati nel namespace corrente perché sono definiti nel package sound.effects al momento di eseguire l’istruzione from...import (funziona allo stesso modo quando la variabile __all__ è definita).

Anche se alcuni moduli sono progettati per esportare solo alcuni nomi, secondo certi criteri, quando importate con import *, questa è comunque considerata una cattiva pratica nel codice «di produzione».

Ricordate che non c’è niente di male a importare from package import specific_submodule. In effetti questo è il modo raccomandato, a meno che il modulo importatore non stia anche importando un altro modulo con lo stesso nome da un altro package.

6.4.2. Riferimenti intra-package

Quando i package contengono a loro volta dei sub-package (come nel caso del nostro esempio sound), potete usare gli import assoluti per riferirvi a moduli di package «cugini». Per esempio, se il modulo sound.filters.vocoder ha bisogno di usare il modulo echo nel package sound.effects, può importarlo con from sound.effects import echo.

Potete anche usare gli import relativi, negli import del tipo from module import name. Gli import relativi usano una notazione con punti iniziali per indicare il package corrente e genitore interessati dall’import. Dal modulo surround, per esempio, potreste importare:

from . import echo
from .. import formats
from ..filters import equalizer

Si noti che gli import relativi si basano sul nome del modulo importatore. Siccome il nome del modulo principale è sempre "__main__", i moduli intesi per essere usati come script (come il modulo principale di un’applicazione Python) devono sempre usare gli import assoluti.

6.4.3. Package in directory multiple

I package hanno un attributo speciale __path__. Questa variabile è una lista, inizializzata con il nome della directory dove risiede il file __init__.py del package, prima che il codice di questo sia eseguito. Potete modificare il contenuto della variabile: così facendo modificate i percorsi di ricerca dei moduli e dei sub-package del package, per tutte le successive importazioni.

Anche se è una funzionalità raramente necessaria, può essere usata per estendere l’insieme dei moduli disponibili in un package.

Note

1

In effetti anche le definizioni di funzione sono delle «istruzioni» che vengono eseguite; l’esecuzione della definizione di una funzione a livello del modulo inserisce il nome della funzione nella tabella dei simboli globale.