Creare Script e moduli¶
Script in Python¶
Uno script è un file contenente istruzioni che il computer esegue automaticamente in base ad un interprete.
L'idea di uno script è quella di scrivere un programma in un file di testo, e poi eseguirlo senza doverlo copiare e incollare ogni volta in un terminale o in un ambiente interattivo. È possibile eseguire uno script automaticamente in modo che il computer possa eseguire un programma anche quando noi non siamo davanti al computer, ad esempio di notte, mentre siamo in vacanza o mentre stiamo facendo qualcosa di più piacevole, come studiare Fisica o andare a farci un giro.
In ricerca è normale avere programmi, che siano di analisi o di simulazione, che richiedono ore, giorni o addirittura settimane per arrivare ad un risultato soddisfacente. Chiaramente, questi non possono essere eseguiti in modo interattivo.
Creare uno script¶
Su Mac e Linux, uno script Python è un file eseguibile contenente una serie di istruzioni Python, e che inizia con la riga
#!/usr/bin/env python3
Questa riga, chiamata shebang, dice al computer di usare un ambiente ("environment") python per interpretare ed eseguire tutto ciò che segue nel file.
In alternativa, su qualsiasi sistema (Windows incluso) si può eseguire uno script contenuto in un file script.py tramite il comando
python3 script.py
dopo aver raggiunto la cartella che contiene il file script.py con il comando cd (change directory) da terminale.
Nota: script.py è un nome preso come esempio. Potete scegliere il nome che volete: ciccio.py, nonpotevoandareafareenologiacheaquestorastavoallapertoomagariallecantineferrari.py, etc.
Bisogna tenete a mente due cose importanti però
- Usate sempre l'estensione
.pyper chiarire che il file contiene codice Python. - Usate nomi corti e comprensibili! Se non riuscite a trovare un nome corto opportuno è possibile che il vostro progetto abbia bisono di essere organizzato. Per esempio, se state simulando un sistema gravitazionale con un diverso numero di corpi ed avete un file
calcola_centro_di_massa_tra_corpi.py, potrebbe aver senso creare una cartellatre_corpied inserire tutti i file relativi a tre corpi al suo interno.
## Un esempio.
## Copiate il codice qui sotto in un file "ciaomondo.py".
## Da terminale, eseguite ``chmod +x ciaomondo.py'' (le virgolette `` e '' non vanno scritte!).
## Fatto questo eseguite ``./ciaomondo.py''
## In alternativa, eseguite ``python3 ciaomondo.py''
#!/usr/bin/env python3
if __name__ == "__main__":
a = 2
b = 3
print(f"Ciao Mondo.\n La somma di a+b fa {a} + {b} = {a+b} ")
Ciao Mondo. La somma di a+b fa 2 + 3 = 5
Esercizio - Creare il tuo primo script¶
Seguite i passi del codice qui sopra:
- Aprite un editor di testo (VS Code, nano, vim, etc.)
- Copiate il codice dell'esempio "Ciao Mondo"
- Salvate il file come
ciaomondo.pynella vostra directory di lavoro- Su Mac/Linux: eseguite
chmod +x ciaomondo.pyseguito da./ciaomondo.py- Su Windows: eseguite
python3 ciaomondo.py- Modificate il codice per stampare anche il prodotto
a * be il quozientea / b- Salvatelo con un nuovo nome (e.g.,
operazioni.py) e rieseguite
IMPORTANTE: Come l'interprete esegue gli script¶
Quando eseguite uno script con l'interprete -- o una cella in un notebook -- succedono due cose.
- L'interprete verifica che la sintassi sia corretta. Questo lo fa scorrendo tutto il file.
- L'interprete esegue le righe una alla volta, controllando solo a questo punto che le funzioni e le variabili chiamate esistano! Questo vuol dire che potrebbe esserci un errore in una riga e questo potrebbe non venire rilevato perché la sua esecuzione dipende da condizioni che verificano solo raramente. Vediamo un esempio.
#!/usr/bin/env python3
#chmod +x ciao_mondo.py
import numpy as np
def ciao_mondo(a,b):
print(f"Ciao Mondo.\n La somma di a+b fa {a} + {b} = {a+b} ")
if __name__ == "__main__":
a = 2
b = 3
ciao_mondo(a,b)
# selezioniamo un numero a caso tra 0 e 1
x = np.random.uniform()
if x>0.999:
print(np.sin(a)) # questa condizione si verifica nel 0.1% dei casi
else:
print(sin(a)) # sin non esiste! Questa riga si esegue nel 99.9% dei casi
Nello script qui sopra la riga print(sin(a)) non è corretta in quanto la funzione sin non è nota. Siccome abbiamo importato numpy chiamandolo np avremmo dovuto scrivere np.sin, come fatto nel ramo if. L'else però viene eseguito nel 99.9% dei casi, per cui nella maggior parte delle esecuzioni il nostro script fallirà con un errore NameError: name 'sin' is not defined. Questo è il tipo di errore che si nasconde durante testing superficiale e può causare problemi quando lasciamo che gli script vengano eseguiti in maniera automatica. Ricordate sempre di testare i vostri script in modo approfondito, e di controllare tutti i rami di esecuzione, anche quelli che si verificano raramente.
NOTA: nei linguaggi compilati, come il C, questi errori sono meno frequenti perché la compilazione comporta un'analisi dettagliata di tutto il codice per poterlo poi trasformare in codice macchina.
Scripts che prendono argomenti¶
Spesso vogliamo passare degli argomenti ad un programma, ad esempio il nome di un file da analizzare, o delle opzioni che specificano un comportamento particolare. Ad esempio da terminale possiamo stampare il contenuto di una directory mydir si usa il comando
ls mydir
dove mydir è un argomento per il programma ls. In Python questo comportamento si ottiene usando il modulo sys. Questo modulo fornisce la variabile argv, che è una lista contenente le stringhe passate alla riga di comando. Il numero di argomenti (equivalente a argc in C) si ottiene con len(sys.argv).
Ricapitolando:
sys.argv: lista contenente le stringhe passate alla riga di comando, incluso il nome dello scriptlen(sys.argv): numero totale di argomenti (equivalente adargcin linguaggi come C)sys.argv[0]è sempre il nome dello script.
Vediamo un esempio
## Un secondo esempio. File con argomenti.
## Copiate il codice qui sotto in un file "ciaomondo_2.py".
## Da terminale, eseguite ``chmod +x ciaomondo_2.py'' (le virgolette `` e '' non vanno scritte!).
## Fatto questo eseguite ``./ciaomondo_2.py''
## In alternativa, eseguite ``python3 ciaomondo_2.py''
#!/usr/bin/env python3
import sys
if __name__ == "__main__":
args = sys.argv # lista degli argomenti da linea di comando. Il primo è il nome dello script!
argc = len(args) # numero argomenti
if (argc < 3):
sys.stderr.write("Lo script richiede due parametri, i numeri a e b da sommare!")
sys.stderr.write(f"Esempio d'uso: {args[0]} 2 4")
sys.stderr.write(f"Aborting.")
sys.exit(1) # Esci dal programma con codice di errore 1 per indicare che qualcosa è andato storto
a = float(args[1])
b = float(args[2])
print(f"Ciao Mondo.\n La somma di a+b fa {a} + {b} = {a+b} ")
sys.exit(0) # esci ritornando zero per indicare che tutto è andato bene
Moduli e pacchetti¶
Un modulo non è altro che un file python (.py) che potete caricare tramite la parola chiave import. Un modulo può contenere:
- Definizioni di funzioni e classi (e decoratori di conseguenza),
- Definizioni di variabili,
- Codice da eseguire nel momento in cui il modulo viene caricato.
- Codice da eseguire nel caso il modulo venga eseguito direttamente da terminale.
Per evitare che il codice venga eseguito da un import, si usa il seguente costrutto:
if __name__ == "__main__":
#codice da eseguire tramite invocazione da shell
il modulo infatti avrà assegnato __main__ come __name__ solo se viene eseguito chiamando direttamente l'interprete dalla shell, o tramite
python3 module.py
oppure ponendo uno she-bang nella prima riga del file:
#!/usr/bin/env python3
L'uso del comando di sistema env è importante perché questo chiama l'interprete attualmente in uso nell'environment python (quindi quello definito dal gestore di pacchetti, es. virtualenv, conda, oppure mamba).
Altre caratteristiche
- il modulo definisce un proprio namespace, ad esempio:
import math
math.sin()
math.cos()
- il namespace può venire rinominato tramite
import <module> as <name>
import numpy as np
np.sin()
np.cos()
- il namespace può venire bypassato tramite
from <module> import *(meglio non farlo)
Gli script funzionano anche come moduli¶
Tutto quello che è fuori dall'if principale può venire caricato da import. Dopodiché possiamo usare il nostro file .py come un modulo, e usare la stessa sintassi che usiamo per i moduli python.
import ciao_mondo
ciao_mondo.ciao_pippo()
Ciao Pippo!
import ciao_mondo as cm
cm.ciao_pippo()
Ciao Pippo!
Pacchetti¶
Un pacchetto è una cartella contenente dei moduli o delle sottocartelle, ed un file __init__.py
└── my_package
├── __init__.py
├── analysis
│ ├── __init__.py
│ ├── analysis.py
│ └── data.py
└── prod
├── MC.py
├── PRNG.py
└── __init__.py
i files __init__.py possono essere tranquillamente vuoti, oppure possono specificare cosa caricare dal pacchetto, l'esecuzione di alcune funzioni, etc (vedere i libri consigliati).
Ad esempio la variabile
__all__= [sin,cos,tan] in __init__.py specifica quali oggetti vengono caricati dal comando from module import *
Questo può essere molto importante per evitare di caricare troppi sottomoduli.
Fate molta attenzione a quanto scritto nel tutorial però.
Usare i sottomoduli¶
All'interno del pacchetto, o in generale se la directory in cui è contenuto è inclusa nella variabile PYTHONPATH (che è modificabile, vedi sotto), possiamo caricare i suoi sottomoduli come segue:
import my_package.analysis.data
oppure
from my_package.analysis import data
Riferimenti INTERNI ai pacchetti¶
All'interno (e solo all'interno...) dei pacchetti i path di riferimento tra moduli funzionano come le directory su linux. Quindi nell'esempio precedente potete importare data.py da analysis.py chiamando
from . import data
e MC.py da prod.py come
from ..prod import MC
Se non ci troviamo nella stessa cartella¶
Se vogliamo accedere a moduli salvati in altre directory, dobbiamo dire a Python dove trovarli, modificando la variabile di sistema PYTHONPATH.
import sys
sys.path.insert(0,'/path/to/mod_directory')