Moduli
In Python un modulo è essenzialmente un file che contiene del codice scritto in questo linguaggio. I moduli sono caratterizzati dall'estensione
.py
, quindi al file istruzione.py
corrisponderà il nome modulo istruzione
.
Lo scopo dei moduli è duplice: essi contengono istruzioni e definizioni che possono essere riutilizzate quante volte si vuole senza la necessità di doverle riscrivere, basterà importare i moduli dove necessari; inoltre, si rivelano particolarmente comodi per gestire facilmente grandi quantità di codice.
Modularizzazione
Modularizzare un software significa scomporlo in più moduli, ciascuno dei quali si occupa di svolgere uno dei compiti del programma. Per comprendere meglio, consideriamo una applicazione che si occupi di plottare l'istogramma di una lista di interi positivi, e di trovarne il valore massimo e l'indice della lista in cui viene raggiunto. Il codice che implementa il programma è scritto e commentato qui di seguito:
"""
Questo script ha il fine di disegnare l'istogramma di una lista di numeri interi positivi e,
al contempo, di determinare il suo valore massimo e il primo indice in cui viene raggiunto.
"""
def positive_integer(vector):
"""
Questa funzione ha come parametro una lista, e si occupa di
verificare che i suoi elementi siano interi positivi.
"""
for val in vector:
if (not isinstance(val, int)) or val <= 0:
raise ValueError('[ERRORE] Il parametro di input deve essere composto ' +
'da numeri interi positivi.')
def real_number(vector):
"""
Questa funzione ha come parametro una lista, e si occupa di
verificare che i suoi elementi siano numeri reali.
"""
for val in vector:
if not isinstance(val, (int, float)):
raise ValueError('[ERRORE] Il parametro di input deve essere composto da numeri reali.')
def hist(vector):
"""
Questa funzione ha come parametro una lista di numeri interi positivi,
e serve a disegnarne il relativo istogramma sul prompt.
"""
try:
positive_integer(vector)
except ValueError as error:
print("[ERRORE] Non è possibile disegnare l'istogramma.")
print(error)
else:
char = '*'
i = 0
for val in vector:
print('vector[{0}]: '.format(i) + char*val)
i = i + 1
def maximize(vector):
"""
Questa funzione ha come parametro una lista di numeri reali,
e serve ha trovarne il valore massimo e il primo indice cui appartiene.
"""
try:
real_number(vector)
except ValueError as error:
print("[ERRORE] Non è possibile calcolare il massimo.")
print(error)
[argmax, max_] = [None, None]
else:
argmax = 0
max_ = 0
i = 0
for val in vector:
if val > max_:
argmax = i
max_ = val
i = i + 1
return [argmax, max_]
# consideriamo una lista di interi positivi
vector = [5, 9, 3, 4]
# plottiamo l'istogramma della suddetta lista
hist(vector)
# determiniamo l'indice in cui viene raggiunto il massimo valore
# e il massimo valore della lista
[argmax, max_] = maximize(vector)
# stampiamo a video il risultato
if argmax != None and max_ != None:
print('argmax: {0}'.format(argmax))
print('max_: {0}'.format(max_))
Eseguendo la suddetta applicazione, otterremo l'output seguente:
vector[0]: *****
vector[1]: *********
vector[2]: ***
vector[3]: ****
argmax: 1
max_: 9
A questo punto, ci accorgiamo subito di una cosa: lo script di cui sopra si compone di un numero ragguardevole di righe di codice. Sorge quindi spontanea una domanda: è possibile scomporre lo script in più file (cioè moduli), ciascuno a sè stante? La risposta è ovviamente sì. Per prima cosa, però, occorre decidere come strutturare la nostra applicazione. Notiamo in particolare che, nel nostro esempio, vi sono:
- due funzioni che si occupano di controllare gli elementi della lista, cioè
positive_integer
ereal_number
; - una funzione che disegna l'istogramma della lista, ovvero
hist
; - una funzione adibita alla ricerca del massimo della lista e dell'indice in cui viene raggiunto, detta
maximize
; - una porzione di codice in cui viene eseguito un test dell'intera applicazione.
~
|__virtualenvs
|__myvenv
|__Include
|__Lib
|__Scripts
|__pyvenv.cfg
|__src
|__validate.py
|__plot.py
|__optimization.py
|__main.py
dove, in particolare:
- il modulo
validate
conterrà le funzioni che si occupano di validare gli elementi della lista; - il modulo
plot
conterrà la funzione che si occupa di disegnare l'istogramma della lista; - il modulo
optimization
conterrà la funzione che determina il massimo e l'argomento del massimo della lista; - il modulo
main
conterrà le istruzioni che verranno eseguite dall'applicazione.
validate
. Esso conterrà le funzioni positive_integer
e real_number
, e pertanto sarà dato da:
"""
Modulo validate: si occupa di validare gli elementi di una lista.
"""
def positive_integer(vector):
"""
Questa funzione ha come parametro una lista, e si occupa di
verificare che i suoi elementi siano interi positivi.
"""
for val in vector:
if (not isinstance(val, int)) or val <= 0:
raise ValueError('[ERRORE] Il parametro di input deve essere composto ' +
'da numeri interi positivi.')
def real_number(vector):
"""
Questa funzione ha come parametro una lista, e si occupa di
verificare che i suoi elementi siano numeri reali.
"""
for val in vector:
if not isinstance(val, (int, float)):
raise ValueError('[ERRORE] Il parametro di input deve essere composto da numeri reali.')
import
statement
Il prossimo modulo da definire è
plot
, il quale dovrà contenere la funzione hist
. Ci accorgiamo però che questa funzione contiene una chiamata alla funzione real_number
che adesso risiede nel modulo validate
. Bisogna quindi fare in modo che, all'interno del modulo plot
, sia possibile riferirsi a tale funzione. A tal fine, Python mette a disposizione l'istruzione import
, da inserire all'inizio del modulo, che consente di importare tutti i moduli necessari alla definizione del file di interesse. In tal caso verrà usata come segue:
"""
Modulo plot: si occupa di disegnare gli elementi di una lista.
"""
# importiamo i moduli necessari
import validate
def hist(vector):
"""
Questa funzione ha come parametro una lista di numeri interi positivi,
e serve a disegnarne il relativo istogramma sul prompt.
"""
try:
# validiamo gli elementi della lista tramite la funzione positive_integer
# del modulo validate importato all'inizio
validate.positive_integer(vector)
except ValueError as error:
print("[ERRORE] Non è possibile disegnare l'istogramma.")
print(error)
else:
char = '*'
i = 0
for val in vector:
print('vector[{0}]: '.format(i) + char*val)
i = i + 1
Si noti l'istruzione
import validate
all'inizio del file, che ci ha consentito di importare il modulo validate
, e l'utilizzo dell'istruzione validate.positive_integer(vector)
per effettuare una chiamata alla funzione positive_integer
del modulo validate
.
from...import
statement
Osserviamo che l'istruzione
import
consente di importare tutte le funzioni e le classi presenti nel modulo importato. Questo può tradursi in un calo delle performance, specie quando ci servono solo poche funzionalità del modulo importato. Proseguendo col nostro esempio, ci accorgiamo che il modulo plot
necessita di importare, dal modulo validate
, la sola funzione positive_integer
. Al fine di importare solo le funzionalità necessarie, Python mette a disposizione lo statement from nome_modulo import funzione_o_classe
, che nel caso del modulo plot
si traduce in quanto segue:
"""
Modulo plot: si occupa di disegnare gli elementi di una lista.
"""
# dal modulo validate importiamo la funzione positive_integer
from validate import positive_integer
def hist(vector):
"""
Questa funzione ha come parametro una lista di numeri interi positivi,
e serve a disegnarne il relativo istogramma sul prompt.
"""
try:
# validiamo gli elementi della lista tramite la funzione positive_integer
# importata all'inizio
positive_integer(vector)
except ValueError as error:
print("[ERRORE] Non è possibile disegnare l'istogramma.")
print(error)
else:
char = '*'
i = 0
for val in vector:
print('vector[{0}]: '.format(i) + char*val)
i = i + 1
Si noti l'istruzione
Seguendo la stessa filosofia, definiamo il modulo
from validate import positive_integer
all'inizio del file, che ci ha consentito di importare la funzione positive_integer
dal modulo validate
. Inoltre, a differenza dello statement import
, la chiamata alla funzione importata non necessita della sintassi validate.positive_integer(vector)
.Seguendo la stessa filosofia, definiamo il modulo
optimization
, il quale necessita, per il calcolo del massimo, della funzione real_numer
del modulo validate
. Pertanto tale modulo sarà così definito:
"""
Modulo optimization: si occupa di risolvere problemi di ottimizzazione.
"""
# dal modulo validate importiamo la funzione real_number
from validate import real_number
def maximize(vector):
"""
Questa funzione ha come parametro una lista di numeri reali,
e serve ha trovarne il valore massimo e il primo indice cui appartiene.
"""
try:
real_number(vector)
except ValueError as error:
print("[ERRORE] Non è possibile calcolare il massimo.")
print(error)
[argmax, max_] = [None, None]
else:
argmax = 0
max_ = 0
i = 0
for val in vector:
if val > max_:
argmax = i
max_ = val
i = i + 1
return [argmax, max_]
__main__
variable
Per terminare la modularizzazione della nostra applicazione, resta da definire il modulo
main
, che sarà il modulo da cui verrà eseguito il programma. Per la sua definizione, è buona norma introdurre una funzione main()
, la quale verrà eseguita solo se il file main.py
viene mandato in esecuzione dall'interprete Python. Quando ciò avviene, l'interprete assegna al fine main.py
una variabile standard __name__
con valore uguale a "__main__"
. Di conseguenza, il modulo si articola come segue:
"""
Modulo main: è il modulo da cui viene lanciata l'applicazione.
"""
# importiamo le funzioni necessarie all'esecuzione del programma
from optimization import maximize
from plot import hist
def main():
"""
Questa funzione contiene le istruzioni che andranno eseguite
quando il file main.py viene mandato in esecuzione.
"""
# consideriamo una lista di interi positivi
vector = [5, 9, 3, 4]
# plottiamo l'istogramma della suddetta lista
hist(vector)
# determiniamo l'indice in cui viene raggiunto il massimo valore
# e il massimo valore della lista
[argmax, max_] = maximize(vector)
# stampiamo a video il risultato
if argmax != None and max_ != None:
print('argmax: {0}'.format(argmax))
print('max_: {0}'.format(max_))
if __name__ == '__main__':
main()
A questo punto, una volta attivato l'ambiente virtuale, potremo lanciare il
main.py
all'interno della cartella src
come al solito:
(myvenv) src > python main.py
e l'output prodotto sarà esattamente quello previsto:
vector[0]: *****
vector[1]: *********
vector[2]: ***
vector[3]: ****
argmax: 1
max_: 9
Vale la pena osservare che, una volta lanciato il file
main.py
, il file system presenterà una nuova cartella, generata automaticamente dall'interprete e denominata __pycache__
, la quale conterrà i moduli validate
, plot
, optimization
(oggetto di importazione da parte del main
) tradotti in linguaggio macchina, e che quindi avranno estensione .pyc
. In particolare, il file system si presenterà così:
~
|__virtualenvs
|__myvenv
|__Include
|__Lib
|__Scripts
|__pyvenv.cfg
|__src
|__validate.py
|__plot.py
|__optimization.py
|__main.py
|__ __pycache__
|__validate.pyc
|__plot.pyc
|__optimization.pyc
as
keyword
È possibile rinominare un modulo, ossia assegnargli un alias, attraverso la parola chiave
as
. Considerando ad esempio il modulo validate
, potremmo importarlo nel modulo plot
col nome val
:
import validate as val
A questo punto potremo chiamare la funzione
positive_integer
all'interno di plot
tramite il comando
val.positive_integer(vector)
Moduli standard
Per comodità degli sviluppatori, Python prevede un gran numero di moduli standard, forniti nativamente dal linguaggio, per svolgere diversi compiti.
Questi moduli sono importabili tramite la stessa sintassi utilizzabile per i moduli definiti dagli utenti.
Per proporre un esempio, possiamo prendere in considerazione il modulo
Per proporre un esempio, possiamo prendere in considerazione il modulo
math
che, come suggerisce il nome, consente di gestire alcune operazioni, funzioni e costanti matematiche di base. Ad esempio, volendo stampare il numero pi-greco, basterà eseguire il seguente script
import math
print(math.pi)
Così come i moduli definiti dagli utenti, anche i moduli standard possono essere rinominati. Nello stesso modo è possibile importare soltanto parti specifiche di un modulo, o tutte le definizioni e le istruzioni che lo compongono.