2.19. Scrivere i moduli

Qualsiasi file .py è un modulo. Suddividere uno script in crescita su alcuni file mantiene ogni file breve e consente di condividere helper comuni tra gli script.

2.19.1. Suddividere uno script

Estrai un gruppo correlato di funzioni in un proprio file:

camera_utils.py

def banner():
    print("OpenMV")

def label(text):
    return "[" + text + "]"

main.py

import camera_utils

camera_utils.banner()
print(camera_utils.label("ready"))

Output:

OpenMV
[ready]

I due file stanno fianco a fianco nella stessa directory. Quando main.py viene eseguito, import camera_utils legge camera_utils.py una volta, esegue le sue istruzioni di livello superiore e lega l’oggetto modulo risultante al nome camera_utils in main. Un secondo import camera_utils da qualsiasi altro punto restituisce lo stesso oggetto – i moduli vengono memorizzati nella cache dopo il loro primo caricamento.

Il nome di un modulo deriva dal suo nome di file, quindi camera_utils.py viene importato come import camera_utils.

2.19.2. Moduli multi-file (pacchetti)

Un modulo può anche essere una directory di file anziché un singolo .py. Il nome della directory diventa il nome del modulo, e i file al suo interno sono i suoi sottomoduli:

camera_utils/
    __init__.py
    text.py
    timing.py

__init__.py è il file che viene eseguito quando il pacchetto stesso viene importato; può essere vuoto, oppure può riesportare nomi selezionati dai sottomoduli. I sottomoduli si raggiungono con un nome puntato:

import camera_utils.text
from camera_utils.timing import elapsed

camera_utils.text.label("ready")

All’interno del pacchetto, i sottomoduli possono raggiungersi a vicenda sia con il nome puntato completo sia con un import relativo che usa un punto iniziale per indicare «questo pacchetto»:

camera_utils/timing.py

from . import text             # the sibling submodule

def stamp(value):
    return text.label(str(value))

Per importare invece un nome specifico, indicalo dopo il fratello puntato:

from .text import label

def stamp(value):
    return label(str(value))

Gli import relativi mantengono un pacchetto autonomo: rinominare la directory del pacchetto non richiede di modificare ogni sottomodulo.

Usa un pacchetto quando un singolo file cresce oltre una dimensione comoda, o quando un insieme di moduli correlati deve stare insieme sotto un unico namespace. Per gli script di tutti i giorni un singolo file .py è sufficiente.

2.19.3. La guardia __name__

Ogni modulo ha un nome integrato __name__. Il suo valore dipende da come viene usato il file:

  • Quando il file viene eseguito direttamente, __name__ è impostato alla stringa "__main__".

  • Quando il file viene importato da un altro script, __name__ è impostato al nome del modulo – il nome del file senza .py.

L’idioma che sfrutta questo è:

label_util.py

def label(text):
    return "[" + text + "]"

if __name__ == "__main__":
    print(label("self-test"))

Output (quando eseguito direttamente):

[self-test]

Quando lo stesso file viene invece importato, __name__ è impostato al nome del modulo, quindi il blocco if viene saltato e non viene eseguito nulla di aggiuntivo:

>>> import label_util
>>> label_util.__name__
'label_util'

Usa questo schema per allegare un rapido smoke test o una demo a un file di libreria senza disturbare gli script che lo importano.