6. Moduli

Se chiudi l’interprete Python e lo riapri, le definizioni che hai fatto (funzioni e variabili) vengono perse. Pertanto, se vuoi scrivere un programma un po” più lungo, è meglio usare un editor di testo per preparare l’input per l’interprete e eseguirlo con quel file come input. Questo è conosciuto come creare uno script. Man mano che il tuo programma diventa più lungo, potresti volerlo dividere in diversi file per una manutenzione più facile. Potresti anche voler usare una funzione utile che hai scritto in diversi programmi senza copiarne la definizione in ciascun programma.

Per supportare questo, Python ha un modo per mettere definizioni in un file e usarle in uno script o in un’istanza interattiva dell’interprete. Tale file è chiamato un modulo; le definizioni di un modulo possono essere importate in altri moduli o nel modulo principale (la collezione di variabili a cui hai accesso in uno script eseguito al livello superiore e in modalità calcolatrice).

Un modulo è un file contenente definizioni e istruzioni Python. Il nome del file è il nome del modulo con il suffisso .py aggiunto. All’interno di un modulo, il nome del modulo (come stringa) è disponibile come valore della variabile globale __name__. Ad esempio, usa il tuo editor di testo preferito per creare un file chiamato fibo.py nella directory corrente con il seguente contenuto:

# Fibonacci numbers module

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return result

Ora entra nell’interprete Python e importa questo modulo con il seguente comando:

>>> import fibo

Questo non aggiunge i nomi delle funzioni definite in fibo direttamente all’attuale namespace (vedi Visibilità e spazi dei nomi in Python per maggiori dettagli); aggiunge solo il nome del modulo fibo lì. Utilizzando il nome del modulo puoi accedere alle funzioni:

>>> 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 intendi usare una funzione spesso puoi assegnarla a 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. Più sui Moduli

Un modulo può contenere istruzioni eseguibili così come definizioni di funzioni. Queste istruzioni sono intese per inizializzare il modulo. Sono eseguite solo la prima volta che il nome del modulo viene incontrato in un’istruzione d’importazione. [1] (Sono anche eseguite se il file è eseguito come script.)

Ogni modulo ha il proprio namespace privato, che è utilizzato come il namespace globale da tutte le funzioni definite nel modulo. Pertanto, l’autore di un modulo può usare variabili globali nel modulo senza preoccuparsi di collisioni accidentali con le variabili globali di un utente. D’altra parte, se sai quello che stai facendo puoi toccare le variabili globali di un modulo con la stessa notazione utilizzata per riferirsi alle sue funzioni, modname.itemname.

I moduli possono importare altri moduli. È consuetudine, ma non obbligatorio, posizionare tutte le istruzioni import all’inizio di un modulo (o script, se è per questo). I nomi dei moduli importati, se posizionati a livello superiore di un modulo (fuori da qualsiasi funzione o classe), sono aggiunti al namespace globale del modulo.

Esiste una variante dell’istruzione import che importa nomi da un modulo direttamente nel namespace del modulo importante. Ad esempio:

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

Questo non introduce il nome del modulo da cui provengono le importazioni nel namespace locale (quindi nell’esempio, fibo non è definito).

Esiste anche una variante per importare tutti i nomi che un modulo definisce:

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

Questo importa tutti i nomi eccetto quelli che iniziano con un underscore (_). Nella maggior parte dei casi i programmatori Python non utilizzano questa funzione poiché introduce un insieme sconosciuto di nomi nell’interprete, nascondendo possibilmente alcune cose che hai già definito.

Nota che in generale la pratica di importare * da un modulo o pacchetto è disapprovata, poiché spesso causa codice poco leggibile. Tuttavia, è accettabile usarla per risparmiare battitura nelle sessioni interattive.

Se il nome del modulo è seguito da as, allora il nome che segue as è legato 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 importa effettivamente il modulo nello stesso modo in cui lo farebbe import fibo, con l’unica differenza che sarà disponibile come fib.

Può anche essere utilizzato quando si utilizza from con effetti simili:

>>> 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 viene importato una sola volta per sessione dell’interprete. Pertanto, se cambi i tuoi moduli, devi riavviare l’interprete – o, se è solo un modulo che vuoi testare interattivamente, usa importlib.reload(), es. import importlib; importlib.reload(modulename).

6.1.1. Esecuzione di moduli come script

Quando esegui un modulo Python con

python fibo.py <arguments>

il codice nel modulo sarà eseguito, proprio come se lo avessi importato, ma con __name__ impostato su "__main__". Ciò significa che aggiungendo questo codice alla fine del tuo modulo:

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

puoi rendere il file utilizzabile sia come script che come modulo importabile, poiché il codice che analizza la linea di comando viene eseguito solo se il modulo è eseguito come file «principale»:

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

Se il modulo è importato, il codice non viene eseguito:

>>> import fibo
>>>

Questo è spesso usato sia per fornire un’interfaccia utente comoda a un modulo, sia per scopi di test (eseguire il modulo come script esegue una suite di test).

6.1.2. Il percorso di ricerca del modulo

Quando un modulo chiamato spam è importato, l’interprete cerca prima un modulo built-in con quel nome. Questi nomi di moduli sono elencati in sys.builtin_module_names. Se non viene trovato, allora cerca un file chiamato spam.py in una lista di directory data dalla variabile sys.path. sys.path è inizializzata da queste posizioni:

  • La directory contenente lo script di input (o la directory corrente quando non è specificato alcun file).

  • PYTHONPATH (una lista di nomi di directory, con la stessa sintassi della variabile di shell PATH).

  • Il valore predefinito dipendente dall’installazione (per convenzione include una directory site-packages, gestita dal modulo site).

Maggiori dettagli sono disponibili in The initialization of the sys.path module search path.

Nota

Su file system che supportano i collegamenti simbolici, la directory contenente lo script di input viene calcolata dopo che il collegamento simbolico è stato seguito. In altre parole la directory contenente il collegamento simbolico non è aggiunta al percorso di ricerca del modulo.

Dopo l’inizializzazione, i programmi Python possono modificare sys.path. La directory contenente lo script in esecuzione è posizionata all’inizio del percorso di ricerca, davanti al percorso della libreria standard. Ciò significa che gli script in quella directory saranno caricati invece dei moduli con lo stesso nome nella directory della libreria. Questo è un errore a meno che la sostituzione non sia intenzionale. Vedi la sezione Moduli Standard per maggiori informazioni.

6.1.3. File Python «compilati»

Per velocizzare il caricamento dei moduli, Python memorizza nella cache la versione compilata di ciascun modulo nella directory __pycache__ sotto il nome module.version.pyc, dove la versione codifica il formato del file compilato; contiene generalmente il numero di versione di Python. Ad esempio, nella versione CPython 3.3, la versione compilata di spam.py verrebbe memorizzata nella cache come __pycache__/spam.cpython-33.pyc. Questa convenzione di denominazione permette ai moduli compilati di versioni diverse e rilasci diversi di Python di coesistere.

Python controlla la data di modifica della sorgente rispetto alla versione compilata per vedere se è obsoleta e deve essere ricompilata. Questo è un processo completamente automatico. Inoltre, i moduli compilati sono indipendenti dalla piattaforma, quindi la stessa libreria può essere condivisa tra sistemi con diverse architetture.

Python non controlla la cache in due circostanze. Primo, ricompila sempre e non memorizza il risultato per il modulo caricato direttamente dalla linea di comando. Secondo, non controlla la cache se non esiste il modulo sorgente. Per supportare una distribuzione senza sorgente (solo compilata), il modulo compilato deve essere nella directory sorgente e non deve esserci un modulo sorgente.

Alcuni consigli per esperti:

  • Puoi usare le opzioni -O o -OO sul comando Python per ridurre la dimensione di un modulo compilato. L’opzione -O rimuove le istruzioni assert, l’opzione -OO rimuove sia le istruzioni assert che le stringhe __doc__. Poiché alcuni programmi possono fare affidamento su queste, dovresti usare questa opzione solo se sai quello che stai facendo. I moduli «ottimizzati» hanno un tag opt- e sono solitamente più piccoli. I futuri rilasci possono cambiare gli effetti dell’ottimizzazione.

  • Un programma non gira più velocemente quando è letto da un file .pyc rispetto a quando è letto da un file .py; l’unica cosa che è più veloce nei file .pyc è la velocità con cui vengono caricati.

  • Il modulo compileall può creare file .pyc per tutti i moduli in una directory.

  • Ci sono maggiori dettagli su questo processo, incluso un diagramma di flusso delle decisioni, in PEP 3147.

6.2. Moduli Standard

Python viene fornito con una libreria di moduli standard, descritta in un documento separato, il Python Library Reference («Library Reference» da qui in avanti). Alcuni moduli sono incorporati nell’interprete; questi forniscono accesso a operazioni che non fanno parte del core del linguaggio ma sono comunque incorporate, sia per efficienza che per fornire accesso a primitive del sistema operativo come le chiamate di sistema. Il set di tali moduli è un’opzione di configurazione che dipende anche dalla piattaforma sottostante. Ad esempio, il modulo winreg è fornito solo sui sistemi Windows. Un particolare modulo merita attenzione: sys, che è incorporato in ogni interprete Python. Le variabili sys.ps1 e sys.ps2 definiscono le stringhe usate come prompt primario e secondario:

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

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

La variabile sys.path è una lista di stringhe che determina il percorso di ricerca dell’interprete per i moduli. È inizializzata a un percorso predefinito preso dalla variabile di ambiente PYTHONPATH, o da un predefinito incorporato se PYTHONPATH non è impostato. Puoi modificarla usando le operazioni standard delle liste:

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

6.3. La funzione dir()

La funzione built-in dir() è usata per scoprire quali nomi definisce un modulo. Restituisce una lista ordinata di stringhe:

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)  
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__',
 '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__',
 '__stderr__', '__stdin__', '__stdout__', '__unraisablehook__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_framework',
 '_getframe', '_git', '_home', '_xoptions', 'abiflags', 'addaudithook',
 'api_version', 'argv', 'audit', 'base_exec_prefix', 'base_prefix',
 'breakpointhook', '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', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth',
 'getallocatedblocks', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile',
 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'is_finalizing', 'last_traceback', 'last_type', 'last_value',
 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'pycache_prefix',
 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr',
 'stdin', 'stdout', 'thread_info', 'unraisablehook', 'version', 'version_info',
 'warnoptions']

Senza argomenti, dir() elenca i nomi che hai definito attualmente:

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

Nota che elenca tutti i tipi di nomi: variabili, moduli, funzioni, ecc.

dir() non elenca i nomi delle funzioni e variabili built-in. Se vuoi una lista di questi, sono definiti nel modulo standard 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. Pacchetti

I pacchetti sono un modo di strutturare il namespace dei moduli di Python usando «nomi di moduli puntati». Ad esempio, il nome del modulo A.B designa un sottomodulo chiamato B in un pacchetto chiamato A. Proprio come l’uso dei moduli salva gli autori di diversi moduli dal preoccuparsi dei nomi delle variabili globali degli altri, l’uso dei nomi di moduli puntati salva gli autori di pacchetti multi-modulo come NumPy o Pillow dal preoccuparsi dei nomi dei moduli degli altri.

Supponiamo che tu voglia progettare una collezione di moduli (un «pacchetto») per la gestione uniforme dei file audio e dei dati audio. Ci sono molti formati di file audio diversi (di solito riconosciuti dalla loro estensione, ad esempio: .wav, .aiff, .au), quindi potresti aver bisogno di creare e mantenere una collezione crescente di moduli per la conversione tra i vari formati di file. Ci sono anche molte operazioni diverse che potresti voler eseguire sui dati audio (come il mixaggio, l’aggiunta di eco, l’applicazione di una funzione equalizzatrice, la creazione di un effetto stereo artificiale), quindi, in aggiunta, scriverai una serie interminabile di moduli per eseguire queste operazioni. Ecco una possibile struttura per il tuo pacchetto (espressa in termini di filesystem gerarchico):

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

Quando importa il pacchetto, Python cerca nelle directory su sys.path cercando la sottodirectory del pacchetto.

I file __init__.py sono richiesti per fare in modo che Python tratti le directory contenenti il file come pacchetti (a meno che non si utilizzi un namespace package, una funzionalità relativamente avanzata). Questo impedisce alle directory con un nome comune, come string, di nascondere inavvertitamente moduli validi che si trovano più avanti nel percorso di ricerca del modulo. Nel caso più semplice, __init__.py può essere solo un file vuoto, ma può anche eseguire codice di inizializzazione per il pacchetto o impostare la variabile __all__, descritta più avanti.

Gli utenti del pacchetto possono importare moduli individuali dal pacchetto, ad esempio:

import sound.effects.echo

Questo carica il sottomodulo sound.effects.echo. Deve essere referenziato con il suo nome completo.:

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

Un modo alternativo di importare il sottomodulo è:

from sound.effects import echo

Questo carica anche il sottomodulo echo, e lo rende disponibile senza il prefisso del pacchetto, quindi può essere usato come segue:

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

Un’ulteriore variazione è importare direttamente la funzione o la variabile desiderata:

from sound.effects.echo import echofilter

Ancora una volta, questo carica il sottomodulo echo, ma rende direttamente disponibile la sua funzione echofilter():

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

Nota che quando usi from package import item, l’elemento può essere un sottomodulo (o sottopacchetto) del pacchetto, o qualche altro nome definito nel pacchetto, come una funzione, classe o variabile. L’istruzione import testa prima se l’elemento è definito nel pacchetto; se non lo è, assume che sia un modulo e tenta di caricarlo. Se non riesce a trovarlo, viene sollevata un’eccezione ImportError.

Al contrario, quando usi una sintassi come import item.subitem.subsubitem, ciascun elemento tranne l’ultimo deve essere un pacchetto; l’ultimo elemento può essere un modulo o un pacchetto ma non può essere una classe, funzione o variabile definita nell’elemento precedente.

6.4.1. Importare * Da un Pacchetto

Ora cosa succede quando l’utente scrive from sound.effects import *? Idealmente, si spera che questo vada in qualche modo nel filesystem, trovi quali sottomoduli sono presenti nel pacchetto e li importi tutti. Questo potrebbe richiedere molto tempo e l’importazione di sottomoduli potrebbe avere effetti collaterali indesiderati che dovrebbero accadere solo quando il sottomodulo viene importato esplicitamente.

L’unica soluzione è che l’autore del pacchetto fornisca un indice esplicito del pacchetto. L’istruzione import utilizza la seguente convenzione: se il codice di __init__.py di un pacchetto definisce una lista denominata __all__, essa è considerata la lista dei nomi dei moduli che dovrebbero essere importati quando si incontra from package import *. È compito dell’autore del pacchetto mantenere aggiornata questa lista quando viene rilasciata una nuova versione del pacchetto. Gli autori dei pacchetti possono anche decidere di non supportarla, se non vedono un’utilità per l’importazione * dal loro pacchetto. Ad esempio, il file sound/effects/__init__.py potrebbe contenere il seguente codice:

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

Questo significherebbe che from sound.effects import * importerebbe i tre sottomoduli denominati del pacchetto sound.effects.

Tieni presente che i sottomoduli potrebbero essere ombreggiati da nomi definiti localmente. Ad esempio, se aggiungi una funzione reverse al file sound/effects/__init__.py, from sound.effects import * importerebbe solo i due sottomoduli echo e surround, ma non il sottomodulo reverse, perché è oscurato dalla funzione reverse definita localmente.:

__all__ = [
    "echo",      # refers to the 'echo.py' file
    "surround",  # refers to the 'surround.py' file
    "reverse",   # !!! refers to the 'reverse' function now !!!
]

def reverse(msg: str):  # <-- this name shadows the 'reverse.py' submodule
    return msg[::-1]    #     in the case of a 'from sound.effects import *'

Se __all__ non è definito, l’istruzione from sound.effects import * non importa tutti i sottomoduli del pacchetto sound.effects nello spazio dei nomi corrente; si limita ad assicurare che il pacchetto sound.effects sia stato importato (eventualmente eseguendo qualsiasi codice di inizializzazione in __init__.py) e quindi importa solo i nomi definiti nel pacchetto. Questo include qualsiasi nome definito (e sottomoduli esplicitamente caricati) nel file __init__.py. Include anche eventuali sottomoduli del pacchetto che sono stati esplicitamente caricati da precedenti istruzioni di import. Considera questo codice:

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

In questo esempio, i moduli echo e surround sono importati nello spazio dei nomi corrente perché sono definiti nel pacchetto sound.effects quando viene eseguita l’istruzione from...import. (Questo funziona anche quando è definito __all__.)

Sebbene certi moduli siano progettati per esportare solo nomi che seguono certi schemi quando usi import *, è comunque considerata una cattiva pratica nel codice di produzione.

Ricorda, non c’è nulla di sbagliato nell’usare from package import specific_submodule! Infatti, questa è la notazione raccomandata a meno che il modulo importante non debba usare sottomoduli con lo stesso nome da pacchetti differenti.

6.4.2. Riferimenti Intra-pacchetto

Quando i pacchetti sono strutturati in sottopacchetti (come il pacchetto sound nell’esempio), puoi usare importazioni assolute per riferirti a sottomoduli di pacchetti fratelli. Ad esempio, se il modulo sound.filters.vocoder deve utilizzare il modulo echo nel pacchetto sound.effects, può usare from sound.effects import echo.

Puoi anche scrivere importazioni relative, con la forma dell’istruzione di importazione from module import name. Queste importazioni utilizzano punti di testa per indicare i pacchetti corrente e genitore coinvolti nell’importazione relativa. Dal modulo surround, ad esempio, potresti usare:

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

Nota che le importazioni relative si basano sul nome del modulo corrente. Poiché il nome del modulo principale è sempre "__main__", i moduli destinati all’uso come modulo principale di un’applicazione Python devono sempre usare importazioni assolute.

6.4.3. Pacchetti in Directory Multiple

Packages support one more special attribute, __path__. This is initialized to be a sequence of strings containing the name of the directory holding the package’s __init__.py before the code in that file is executed. This variable can be modified; doing so affects future searches for modules and subpackages contained in the package.

Sebbene questa funzione non sia spesso necessaria, può essere utilizzata per estendere il set di moduli trovati in un pacchetto.

Note