Capitolo 5 · Funzioni¶
Cosa impari
- a dividere un programma in funzioni, a definirle con
defe a chiamarle; - a organizzare un programma con la funzione
maine la progettazione top-down; - che cosa sono le variabili locali, i parametri e gli argomenti;
- perché evitare le variabili globali e quando usare le costanti globali;
- a scrivere funzioni che restituiscono un valore con
return; - a usare i moduli della libreria standard
randomemath, e a scrivere moduli tuoi; - a far partire
mainsolo quando serve, conif __name__ == '__main__':.
Finora hai scritto programmi come una lunga sequenza di istruzioni. Quando il problema cresce, un programma così diventa difficile da leggere e da correggere. Le funzioni permettono di dividere un problema grande in compiti piccoli, ognuno con un nome, da scrivere una volta e usare tutte le volte che serve. All'esame dovrai proprio scrivere funzioni con nomi e parametri indicati nella traccia.
Che cos'è una funzione¶
Una funzione è un gruppo di istruzioni che svolge un compito specifico. Di solito quel compito è una parte di un programma più ampio.
È l'approccio divide et impera (dividi e conquista): un problema grande si spezza in problemi piccoli, più facili da risolvere. Un programma in cui ogni compito sta nella sua funzione si chiama programma modularizzato.
| Senza funzioni | Con le funzioni |
|---|---|
| una lunga sequenza di istruzioni, tutte di seguito | il programma è diviso in compiti piccoli, ognuno svolto da una funzione |
| per ripetere un compito devi copiare le istruzioni | scrivi il compito una volta e lo chiami quando serve |
I vantaggi principali:
- riuso del codice: scrivi una volta, chiami molte volte;
- codice più semplice: ogni funzione risolve un piccolo problema;
- test e debug più facili: puoi provare e correggere ogni funzione da sola;
- sviluppo più rapido: problemi più piccoli, soluzioni più veloci;
- lavoro di squadra: persone diverse possono scrivere funzioni diverse.
Funzioni void e funzioni con valore¶
In Python esistono due tipi di funzioni:
- una funzione void esegue le sue istruzioni e termina, senza restituire nulla;
- una funzione che restituisce un valore esegue le sue istruzioni e poi restituisce un risultato a chi l'ha chiamata.
# Funzione void: esegue e termina
def saluta():
print('Ciao!')
# Funzione con valore: restituisce un risultato
def doppio(n):
return n * 2
saluta() # stampa Ciao!
x = doppio(5) # x vale 10
print(x)
Conosci già alcune funzioni che restituiscono un valore: input(), int() e float(). Ad esempio int('12') restituisce il numero 12. Per ora concentriamoci sulle funzioni void; le funzioni con return le vediamo più avanti.
Definire e chiamare una funzione¶
Per creare una funzione scrivi la sua definizione:
defè la parola chiave che introduce la definizione;- poi c'è il nome della funzione, seguito da parentesi e due punti;
- tutta la prima riga, da
defai due punti, si chiama intestazione; - sotto c'è il blocco: le istruzioni rientrate che la funzione esegue.
Il nome deve descrivere il compito, quindi spesso contiene un verbo: calcola_media, stampa_menu, leggi_dati. Le regole sono le stesse dei nomi di variabile:
| Regola | Esempi |
|---|---|
| non può essere una parola chiave | if non va bene |
| non può contenere spazi | calcola media non va bene, calcola_media sì |
il primo carattere è una lettera o un underscore _ |
_totale va bene, 2somma no |
| gli altri caratteri sono lettere, cifre o underscore | somma2 va bene |
| maiuscole e minuscole sono diverse | Somma e somma sono due nomi diversi |
Scrivere la definizione non esegue la funzione: la prepara soltanto. Per eseguirla devi chiamarla, cioè scrivere il suo nome seguito dalle parentesi.
Quando Python incontra una chiamata:
- salta alla funzione;
- esegue le istruzioni del suo blocco;
- torna al punto del programma da cui era partito (il chiamante) e continua da lì.
L'output è Inizio, poi le due righe della funzione, poi Fine. Prova a chiamare saluta() due volte di seguito: le due righe vengono stampate due volte.
Prima la definizione, poi la chiamata
Python legge il file dall'alto verso il basso. Se chiami una funzione prima della riga in cui la definisci, ottieni un NameError: in quel momento Python non la conosce ancora.
La funzione main¶
Molti programmi hanno una funzione chiamata main: è la funzione principale. Contiene la logica generale del programma e chiama le altre funzioni quando servono. È come un direttore d'orchestra.
Alla fine del file c'è una sola istruzione fuori dalle funzioni: la chiamata a main(), che fa partire tutto.
# Funzione principale
def main():
print("Benvenuto")
saluta()
print("Fine")
def saluta():
print("Ciao!")
# Il programma parte da qui
main()
Nota che main chiama saluta, che è definita più in basso. Non è un errore: quando viene eseguita la riga main(), Python ha già letto tutte le definizioni.
L'indentazione¶
Python usa l'indentazione (il rientro) per capire dove inizia e dove finisce il blocco di una funzione, come per if e while.
- Tutte le righe del blocco devono avere lo stesso rientro (di solito 4 spazi).
- Non mescolare tabulazioni e spazi: l'interprete si può confondere.
- Le righe vuote dentro un blocco vengono ignorate.
# Corretto
def saluta():
print("Ciao")
print("Benvenuto")
# Errato: la seconda riga ha un rientro diverso
def saluta():
print("Ciao")
print("Benvenuto") # IndentationError
Progettare un programma con le funzioni¶
Nei diagrammi di flusso la chiamata a una funzione si disegna con un rettangolo con due barre verticali sui lati, con il nome della funzione al centro. Ogni funzione ha poi un suo diagramma separato, che termina con il simbolo terminale Return (ritorno al chiamante).
I programmatori usano spesso la progettazione top-down (dall'alto verso il basso): si parte dal compito generale e lo si divide in compiti sempre più piccoli, finché ognuno diventa una funzione semplice da scrivere.
Le relazioni tra le funzioni si disegnano con un diagramma gerarchico (o diagramma a struttura):
- c'è un riquadro per ogni funzione del programma;
- le linee collegano ogni funzione alle funzioni che chiama;
- non mostra le istruzioni interne delle funzioni.
Ecco il diagramma gerarchico di un programma che calcola la paga di un dipendente:
main
|
+--------------+-----+---------+--------------+
| | | |
get_hours get_pay_rate calc_gross_pay print_pay
main delega i compiti a quattro funzioni: leggere le ore, leggere la paga oraria, calcolare la paga lorda, mostrare il risultato.
L'istruzione pass¶
Quando progetti in modo top-down, all'inizio conosci i nomi delle funzioni ma non hai ancora scritto il loro codice. Una funzione però non può avere il blocco vuoto: sarebbe un errore. Si usa allora l'istruzione pass, che non fa nulla e serve da segnaposto. Una funzione che contiene solo pass si chiama stub.
def main():
print('Inizio programma')
calcola_totale()
mostra_risultato()
print('Fine programma')
def calcola_totale():
pass # da implementare
def mostra_risultato():
pass # da implementare
main()
Il programma funziona già: puoi controllare la sua struttura e poi completare una funzione alla volta. È la strategia consigliata anche all'esame.
Variabili locali¶
Una variabile creata dentro una funzione è una variabile locale: solo quella funzione può usarla.
Lo scope (ambito di visibilità) di una variabile è la parte del programma in cui la variabile si può usare. Lo scope di una variabile locale è la funzione in cui è creata.
L'esempio che segue mostra apposta un errore: fuori da saluta la variabile messaggio non esiste.
def saluta():
messaggio = "Ciao" # locale rispetto a saluta()
print(messaggio)
saluta()
print(messaggio) # NameError: qui messaggio non esiste
Due regole:
- una variabile locale deve essere creata prima di essere usata;
- funzioni diverse possono avere variabili locali con lo stesso nome: sono variabili diverse, che non si disturbano.
def funzione_a():
totale = 10 # locale rispetto a funzione_a()
print(totale)
def funzione_b():
totale = 99 # un altro totale, locale rispetto a funzione_b()
print(totale)
funzione_a()
funzione_b()
Passare argomenti alle funzioni¶
Spesso una funzione ha bisogno di dati per lavorare. Un argomento è un dato che il programma invia alla funzione quando la chiama. Si scrive tra le parentesi, dopo il nome della funzione.
def show_double(number):
result = number * 2
print(result)
# Passa 5 come argomento
show_double(5)
show_double(21)
Argomento e parametro¶
Sono i due lati della stessa comunicazione:
| Chi è | Dove si scrive | |
|---|---|---|
| argomento | chi invia: il valore passato nella chiamata | show_double(5): 5 è un argomento |
| parametro | chi riceve: la variabile a cui viene assegnato il valore dell'argomento | def show_double(number): number è un parametro |
Anche una variabile può essere usata come argomento. In questo caso, durante la chiamata, value (nel chiamante) e number (nella funzione) fanno riferimento allo stesso valore in memoria.
def main():
value = 5
show_double(value)
def show_double(number):
result = number * 2
print(result)
main()
Un parametro è una variabile locale della funzione: il suo scope è la funzione stessa.
Più argomenti¶
Una funzione può avere più parametri, separati da virgole. Gli argomenti vengono associati ai parametri in base alla posizione: il primo argomento va al primo parametro, il secondo al secondo, e così via.
def mostra_dati(num, testo, pi):
print(num, testo, pi)
def show_sum(num1, num2):
result = num1 + num2
print(result)
mostra_dati(5, "abc", 3.14) # num=5, testo="abc", pi=3.14
show_sum(12, 45) # num1=12, num2=45
Passaggio per valore¶
Se dentro la funzione assegni un nuovo valore al parametro, la variabile del chiamante non cambia. Il parametro diventa un riferimento a un valore nuovo, ma solo dentro la funzione. Questo comportamento si chiama passaggio per valore (pass by value).
def change_me(arg):
print('Nella funzione arg vale', arg)
arg = 0
print('Ora arg vale', arg)
value = 99
change_me(value)
print('Dopo la chiamata value vale', value) # ancora 99
All'inizio value e arg fanno riferimento allo stesso valore 99. L'istruzione arg = 0 cambia solo arg; value continua a valere 99.
Come far arrivare un risultato al chiamante
Se vuoi che la funzione "mandi indietro" un risultato, non modificare il parametro: usa return, che vediamo tra poco.
Argomenti con parola chiave¶
Puoi anche indicare per nome a quale parametro va un valore: nome_funzione(parametro=valore). Questo si chiama argomento con parola chiave (keyword argument). Così l'ordine degli argomenti non conta più.
Puoi anche mescolare i due modi, ma gli argomenti posizionali vanno prima di quelli con parola chiave.
def show_interest(princ, rate, periods):
interest = princ * rate * periods
print(f'Interesse semplice: {interest:.2f} euro')
# Argomenti posizionali
show_interest(10000, 0.05, 10)
# Argomenti con parola chiave: l'ordine non conta
show_interest(periods=10, princ=10000, rate=0.05)
# Misto: prima i posizionali
show_interest(10000, 0.05, periods=10)
Le tre chiamate danno lo stesso risultato: 5000.00 euro.
Variabili globali e costanti globali¶
Una variabile globale è creata con un'assegnazione scritta fuori da tutte le funzioni. Si può leggere da qualunque punto del file, anche dentro le funzioni.
Per modificare una variabile globale dentro una funzione devi dichiararla con la parola chiave global. Senza global, l'assegnazione creerebbe una nuova variabile locale con lo stesso nome.
# Variabile globale
totale = 0
def aggiungi(valore):
global totale
totale += valore
aggiungi(5)
aggiungi(3)
print(totale) # 8
Funziona, ma di solito conviene evitare le variabili globali:
- sono difficili da correggere: se il valore è sbagliato, qualunque punto del programma potrebbe averlo cambiato;
- rendono le funzioni dipendenti: una funzione che usa una variabile globale è difficile da riusare in un altro programma;
- rendono il codice meno leggibile: per capire una funzione devi guardare tutto il resto del file.
Costanti globali¶
C'è un'eccezione accettabile: le costanti globali. Una costante globale è un nome globale che fa riferimento a un valore che non deve cambiare durante il programma, come un'aliquota o il valore di π. Per convenzione si scrive in MAIUSCOLO. Le funzioni la leggono ma non la modificano, quindi non causa i problemi delle variabili globali e rende il codice più leggibile.
# Costanti globali
TASSA_IVA = 0.22
PI_GRECO = 3.14159
def calcola_iva(importo):
return importo * TASSA_IVA
def area_cerchio(raggio):
return PI_GRECO * raggio ** 2
print(f'IVA su 100 euro: {calcola_iva(100):.2f}')
print(f'Area di un cerchio di raggio 2: {area_cerchio(2):.2f}')
Se l'aliquota cambia, basta modificare una riga in cima al file.
La libreria standard e i moduli¶
Python ha una libreria standard: una raccolta di funzioni già scritte per i compiti più comuni. Alcune sono funzioni integrate (built-in), pronte all'uso: print(), input(), range(), len(), int().
Queste funzioni si usano come una scatola nera: non serve sapere come sono scritte dentro. Basta sapere che cosa fanno, che cosa ricevono (l'input) e che cosa restituiscono (l'output).
Molte altre funzioni stanno nei moduli. Un modulo è un file che contiene un insieme di funzioni correlate. Per usarlo lo devi caricare con l'istruzione import, che si scrive all'inizio del programma:
Poi chiami le sue funzioni con la notazione del punto (dot notation): nome_modulo.nome_funzione(argomenti).
Numeri casuali: il modulo random¶
Il modulo random contiene funzioni per generare numeri casuali. Servono nei giochi, nelle simulazioni, nei test.
La funzione random.randint(min, max) restituisce un intero casuale tra min e max, entrambi inclusi. Il numero restituito si può usare ovunque si usa un intero: puoi assegnarlo a una variabile o passarlo direttamente a print.
import random
# Tiriamo un dado a 6 facce
dado = random.randint(1, 6)
print('Dado:', dado)
# Estraiamo 3 numeri tra 1 e 100
for i in range(3):
print(random.randint(1, 100))
I numeri sono casuali: l'output cambia da un'esecuzione all'altra. Premi più volte Esegui per vedere valori diversi.
Altre funzioni del modulo random:
| Funzione | Che cosa restituisce | Esempio |
|---|---|---|
randint(min, max) |
un intero tra min e max, inclusi |
random.randint(1, 6) |
randrange(start, stop, step) |
un intero scelto a caso dalla sequenza che range produrrebbe con gli stessi argomenti |
random.randrange(100) → da 0 a 99 |
random() |
un float tra 0.0 (incluso) e 1.0 (escluso); non riceve argomenti | random.random() |
uniform(min, max) |
un float casuale tra min e max |
random.uniform(1.0, 5.0) |
import random
print(random.randrange(100)) # intero tra 0 e 99
print(random.randrange(0, 101, 10)) # 0, 10, 20, ..., 100
print(random.random()) # float tra 0.0 e 1.0
print(random.uniform(1.0, 5.0)) # float tra 1.0 e 5.0
Anche qui l'output cambia a ogni esecuzione.
Il seme dei numeri casuali¶
In realtà i numeri di random sono pseudo-casuali: sono calcolati da una formula matematica a partire da un valore iniziale, il seme (seed). Di solito Python usa come seme l'orario del sistema, quindi la sequenza cambia ogni volta.
Con random.seed(valore) scegli tu il seme: con lo stesso seme ottieni sempre la stessa sequenza. È utile quando vuoi ripetere esattamente un esperimento o controllare un programma.
Questo esempio stampa sempre 74 e 5. Prova a cambiare il seme in random.seed(20): ottieni un'altra sequenza, ma sempre la stessa a ogni esecuzione.
Scrivere funzioni che restituiscono un valore¶
Una funzione può restituire un valore al chiamante con l'istruzione return:
Quando Python esegue return, calcola l'espressione, restituisce il risultato al chiamante e la funzione termina. L'espressione può essere un valore, una variabile o un calcolo.
def somma(num1, num2): # intestazione
risultato = num1 + num2 # corpo
return risultato # istruzione return
totale = somma(3, 7)
print(totale) # 10
Lo stesso risultato si ottiene con una riga sola: return num1 + num2.
Il valore restituito si può usare in tre modi:
- assegnarlo a una variabile, per usarlo dopo:
x = somma(3, 5); - usarlo in un'espressione:
tot = somma(a, b) * 2; - passarlo a un'altra funzione:
print(somma(4, 6)).
def somma(a, b):
return a + b
x = somma(3, 5)
print(x) # 8
tot = somma(x, 2) * 2
print(tot) # 20
print(somma(4, 6)) # 10
Restituire non è stampare
print mostra un valore sullo schermo, ma il chiamante non lo riceve. return consegna il valore al chiamante, che può usarlo nei calcoli. Se una funzione non esegue nessun return, restituisce il valore speciale None.
Esempio: il prezzo scontato¶
Questo programma usa una costante globale e due funzioni che restituiscono un valore. È diviso in compiti piccoli, come nella progettazione top-down.
# Percentuale di sconto (costante globale)
DISCOUNT_PERCENTAGE = 0.20
def main():
reg_price = get_regular_price()
sale_price = reg_price - discount(reg_price)
print(f'Il prezzo scontato è {sale_price:.2f} euro')
# Chiede il prezzo pieno e lo restituisce
def get_regular_price():
price = float(input('Inserisci il prezzo pieno: '))
return price
# Riceve il prezzo pieno e restituisce lo sconto
def discount(price):
return price * DISCOUNT_PERCENTAGE
main()
Prova con 100: lo sconto è 20 e il prezzo scontato 80.00 euro.
I diagrammi IPO¶
Un diagramma IPO aiuta a progettare e documentare una funzione. Descrive con frasi brevi (non con il codice) tre cose:
- Input: che cosa riceve la funzione (parametri e dati in ingresso);
- Processing (elaborazione): che cosa fa;
- Output: che cosa restituisce al chiamante.
| Funzione | Input | Elaborazione | Output |
|---|---|---|---|
get_regular_price |
nessuno | chiede all'utente il prezzo pieno | il prezzo pieno |
discount |
il prezzo pieno | calcola il prezzo per la percentuale di sconto | lo sconto |
Funzioni che restituiscono stringhe¶
Una funzione può restituire qualunque tipo di dato, anche una stringa. Ad esempio, per leggere un dato dall'utente, per costruire un messaggio o per formattare un testo.
def get_name():
# Chiede il nome all'utente
nome = input("Digita il tuo nome: ")
# Restituisce il nome
return nome
utente = get_name()
print("Ciao " + utente) # concatenazione
print("Ciao %s" % utente) # operatore %
print(f"Ciao {utente}") # f-string
Funzioni booleane¶
Una funzione booleana restituisce True o False. Serve a verificare una condizione, e il risultato si usa direttamente in un if o in un while. Il nome spesso è una domanda: is_even (è pari?), voto_valido, posti_sufficienti.
def is_even(numero):
if numero % 2 == 0:
return True
else:
return False
n = int(input('Inserisci un numero intero: '))
if is_even(n):
print(n, 'è pari')
else:
print(n, 'è dispari')
Le funzioni booleane sono molto comode nella validazione dell'input: il ciclo diventa facile da leggere.
def voto_valido(voto):
return voto >= 18 and voto <= 30
voto = int(input('Voto (18-30): '))
while not voto_valido(voto):
print('ERRORE: il voto deve essere compreso tra 18 e 30.')
voto = int(input('Voto (18-30): '))
print('Voto registrato:', voto)
Qui return voto >= 18 and voto <= 30 restituisce direttamente il valore della condizione, che è già True o False.
Restituire più valori¶
Una funzione può restituire più valori, separati da virgole: return espr1, espr2, espr3. Nel chiamante li ricevi con un'assegnazione con più variabili a sinistra, nello stesso ordine. Questo si chiama unpacking (spacchettamento).
def get_numeri():
return 1, 2, 3
x, y, z = get_numeri()
print(x, y, z) # 1 2 3
def get_nome_cognome():
nome = input('Nome: ')
cognome = input('Cognome: ')
return nome, cognome
primo, secondo = get_nome_cognome()
print('Benvenuto', primo, secondo)
Il numero di variabili a sinistra deve essere uguale al numero di valori restituiti.
Il valore None¶
Il valore speciale None significa "nessun valore". Si usa, ad esempio:
- per segnalare che la funzione non può dare un risultato valido (un errore);
- per creare una variabile senza darle ancora un valore vero.
def divide(a, b):
if b == 0:
return None # errore: divisione per zero
return a / b
print(divide(10, 4)) # 2.5
risultato = divide(10, 0)
if risultato == None:
print('Divisione impossibile')
Nota che dopo un return la funzione termina subito: se b vale 0, l'istruzione return a / b non viene eseguita.
Il modulo math¶
Il modulo math fa parte della libreria standard e contiene funzioni matematiche pronte. Ogni funzione riceve uno o più numeri, esegue il calcolo e restituisce il risultato. Per usarlo serve import math.
| Funzione | Descrizione | Esempio |
|---|---|---|
sqrt(x) |
radice quadrata di x |
math.sqrt(16) → 4.0 |
ceil(x) |
arrotonda per eccesso | math.ceil(2.3) → 3 |
floor(x) |
arrotonda per difetto | math.floor(2.8) → 2 |
pow(x, y) |
x elevato a y |
math.pow(2, 3) → 8.0 |
exp(x) |
e elevato a x |
math.exp(1) → 2.718... |
log(x) |
logaritmo naturale di x |
math.log(math.e) → 1.0 |
log10(x) |
logaritmo in base 10 | math.log10(100) → 2.0 |
sin(x), cos(x) |
seno e coseno (x in radianti) |
math.sin(0) → 0.0 |
radians(gradi) |
converte gradi in radianti | math.radians(180) → 3.14159... |
Il modulo contiene anche le costanti math.pi (π) e math.e (il numero di Nepero).
import math
print(math.sqrt(16))
print(math.ceil(2.3), math.floor(2.8))
print(math.pow(2, 3))
print(math.log10(100))
# Ipotenusa di un triangolo rettangolo
a = float(input('Cateto a: '))
b = float(input('Cateto b: '))
c = math.sqrt(a ** 2 + b ** 2)
print(f'Ipotenusa: {c:.2f}')
Prova con 3 e 4: l'ipotenusa è 5.00.
Scrivere i tuoi moduli¶
Quando un programma diventa grande, conviene raggruppare le funzioni correlate in moduli separati. Il codice diventa:
- organizzato: più facile da capire;
- testabile: puoi provare un modulo alla volta;
- manutenibile: una modifica resta dentro un modulo;
- riusabile: lo stesso modulo si importa in altri programmi.
Un modulo è semplicemente un file con estensione .py che contiene definizioni di funzioni. Il nome segue le regole dei nomi di variabile: lettere, cifre e underscore, non inizia con una cifra, non è una parola chiave. Il file deve terminare con .py.
Il modulo definisce le funzioni; il programma che lo importa le chiama. Per importare il tuo modulo scrivi il nome del file senza .py.
I due file devono stare nella stessa cartella. Eseguendo programma.py ottieni 78.5 31.400000000000002 (le piccole imprecisioni dei numeri reali le hai viste nella teoria sulla rappresentazione in binario).
Provalo con più file
Gli esempi eseguibili di questa pagina contengono un solo file. Per provare un modulo tuo apri il Foglio libero: con il pulsante + file aggiungi calcoli.py, copia le funzioni e scrivi import calcoli nel file principale. Anche i laboratori delle pagine Esercitazioni ed Esame hanno più file: all'esame il programma è diviso proprio così, con le funzioni in un modulo separato da main.py.
Programmi guidati da menu¶
Un programma guidato da menu mostra un elenco di operazioni, l'utente ne sceglie una e il programma esegue quella scelta. I passi sono:
- mostra il menu con una serie di
print; - legge la scelta con
input; - esegue l'operazione con una struttura
if/elif; - ripete con un ciclo
while, finché l'utente non sceglie di uscire.
Con le funzioni ogni passo ha il suo nome e il ciclo principale resta corto.
import math
def mostra_menu():
print('=== MENU PRINCIPALE ===')
print('1. Calcola l\'area')
print('2. Calcola il perimetro')
print('3. Esci')
def area_cerchio(raggio):
return math.pi * raggio ** 2
def perimetro_cerchio(raggio):
return 2 * math.pi * raggio
def main():
scelta = 0
while scelta != 3:
mostra_menu()
scelta = int(input('Scegli un\'opzione: '))
if scelta == 1:
r = float(input('Raggio: '))
print(f'Area: {area_cerchio(r):.2f}')
elif scelta == 2:
r = float(input('Raggio: '))
print(f'Perimetro: {perimetro_cerchio(r):.2f}')
elif scelta == 3:
print('Arrivederci!')
else:
print('Scelta non valida.')
main()
Prova la sequenza 1, 2 (area 12.57), poi 2, 2 (perimetro 12.57), poi 7 (non valida) e infine 3.
Eseguire main solo quando serve¶
Un file Python si può usare in due modi:
- eseguito direttamente, come programma: in questo caso
main()deve partire; - importato da un altro programma, che vuole solo riusare le sue funzioni: in questo caso
main()non deve partire da solo.
Come fa il file a sapere in quale situazione si trova? Quando Python carica un file, crea automaticamente la variabile speciale __name__ (con due underscore prima e dopo):
| Come viene caricato il file | Valore di __name__ |
|---|---|
| eseguito direttamente | '__main__' |
| importato come modulo | il nome del modulo, ad esempio 'calcoli' |
Qui il file è eseguito direttamente, quindi vedi __main__.
Per far partire main() solo quando il file è il programma principale si usa questo schema:
def main():
print('Eseguo il programma principale')
print('Area:', area_quadrato(4))
def area_quadrato(lato):
return lato * lato
# Chiama main SOLO se il file è eseguito come programma principale
if __name__ == '__main__':
main()
- Se esegui il file, la condizione è vera e
main()parte. - Se un altro programma scrive
importdi questo file, la condizione è falsa:main()non parte, ma l'altro programma può usarearea_quadrato.
Prova con il Foglio libero
Nel Foglio libero crea con + file un modulo geometria.py con questo codice. Nel file principale scrivi import geometria e print(geometria.area_quadrato(5)): vedi solo 25, perché il main di geometria non parte. Poi togli l'if dal modulo e lascia solo main(): ora parte anche il suo main.
Funzioni e grafica con turtle¶
Le funzioni rendono modulare anche il codice grafico. Il modulo turtle disegna su una finestra spostando una "tartaruga" con la penna. Se racchiudi le istruzioni di disegno in una funzione con parametri, puoi disegnare tante figure diverse con una sola riga ciascuna.
Da provare sul tuo computer
turtle apre una finestra grafica, quindi non funziona nel laboratorio di questo sito. Prova questi esempi in VSCodium, scrivendo import turtle all'inizio del file.
import turtle
# Disegna un quadrato pieno con l'angolo in basso a sinistra in (x, y)
def square(x, y, width, color):
turtle.penup() # alza la penna
turtle.goto(x, y) # va al punto di partenza
turtle.fillcolor(color) # imposta il colore di riempimento
turtle.pendown() # abbassa la penna
turtle.begin_fill() # inizia il riempimento
for count in range(4):
turtle.forward(width)
turtle.left(90)
turtle.end_fill() # termina il riempimento
# Disegna un cerchio pieno con centro in (x, y)
def circle(x, y, radius, color):
turtle.penup()
turtle.goto(x, y - radius) # turtle.circle parte dal punto più in basso
turtle.fillcolor(color)
turtle.pendown()
turtle.begin_fill()
turtle.circle(radius)
turtle.end_fill()
# Disegna un segmento da (startX, startY) a (endX, endY)
def line(startX, startY, endX, endY, color):
turtle.penup()
turtle.goto(startX, startY)
turtle.pendown()
turtle.pencolor(color)
turtle.goto(endX, endY)
# Tre quadrati: posizione (x, y), lato, colore
square(100, 0, 50, 'red')
square(-150, -100, 200, 'blue')
square(-200, 150, 75, 'green')
# Tre cerchi: centro (x, y), raggio, colore
circle(0, 0, 100, 'red')
circle(-150, -75, 50, 'blue')
circle(-200, 150, 75, 'green')
# Un triangolo: i vertici sono costanti con un nome
TOP_X = 0
TOP_Y = 100
BASE_LEFT_X = -100
BASE_LEFT_Y = -100
BASE_RIGHT_X = 100
BASE_RIGHT_Y = -100
line(TOP_X, TOP_Y, BASE_LEFT_X, BASE_LEFT_Y, 'red')
line(TOP_X, TOP_Y, BASE_RIGHT_X, BASE_RIGHT_Y, 'blue')
line(BASE_LEFT_X, BASE_LEFT_Y, BASE_RIGHT_X, BASE_RIGHT_Y, 'green')
turtle.done() # lascia aperta la finestra
I vantaggi sono quelli di ogni funzione: posizione, dimensione e colore sono parametri, il codice si riusa e il nome della funzione dice subito che cosa fa.
Errori tipici¶
| Errore | Cosa succede | Come si corregge |
|---|---|---|
def saluta() senza : |
SyntaxError: expected ':' |
aggiungi i due punti alla fine dell'intestazione |
chiamare saluta senza parentesi |
nessun errore, ma la funzione non viene eseguita | scrivi saluta() |
| chiamare la funzione prima della sua definizione | NameError: name '...' is not defined |
definisci le funzioni in alto e chiama main() in fondo al file |
| usare fuori dalla funzione una variabile locale | NameError |
restituisci il valore con return e assegnalo a una variabile |
print dentro la funzione al posto di return |
il chiamante riceve None |
usa return risultato |
| passare meno argomenti dei parametri | TypeError: ... missing 1 required positional argument |
passa un argomento per ogni parametro, nell'ordine giusto |
show_sum(num1=12, 45) |
SyntaxError: un argomento posizionale dopo uno con parola chiave |
prima i posizionali: show_sum(12, num2=45) |
modificare una globale senza global |
si crea una variabile locale; la globale non cambia (o UnboundLocalError) |
evita le variabili globali: passa il valore come argomento e usa return |
import calcoli.py |
ModuleNotFoundError |
il nome del modulo si scrive senza .py: import calcoli |
randint(1, 6) senza random. |
NameError |
usa la notazione del punto: random.randint(1, 6) |
Esercizi¶
Esercizio 1 · Area del rettangolo. Scrivi una funzione area_rettangolo(base, altezza) che restituisce l'area. Nel main chiedi base e altezza all'utente, chiama la funzione e mostra il risultato con due decimali.
Soluzione
def area_rettangolo(base, altezza):
return base * altezza
def main():
base = float(input('Base: '))
altezza = float(input('Altezza: '))
area = area_rettangolo(base, altezza)
print(f'Area del rettangolo: {area:.2f}')
main()
Con base 4 e altezza 2.5 ottieni 10.00. La funzione non usa input né print: riceve i dati come parametri e restituisce il risultato. Così si può riusare in qualunque programma.
Esercizio 2 · Indovina il numero. Il programma sceglie un numero casuale tra 1 e 20 con random.randint. L'utente prova a indovinarlo; a ogni tentativo il programma dice Troppo alto o Troppo basso. Alla fine mostra quanti tentativi sono serviti. Scrivi una funzione confronta(tentativo, segreto) che restituisce la stringa da mostrare ('Troppo alto', 'Troppo basso' oppure 'Indovinato!').
Soluzione
import random
def confronta(tentativo, segreto):
if tentativo > segreto:
return 'Troppo alto'
elif tentativo < segreto:
return 'Troppo basso'
else:
return 'Indovinato!'
def main():
segreto = random.randint(1, 20)
tentativi = 0
tentativo = 0
while tentativo != segreto:
tentativo = int(input('Indovina il numero (1-20): '))
tentativi += 1
print(confronta(tentativo, segreto))
print('Tentativi:', tentativi)
main()
Il numero segreto cambia a ogni esecuzione. tentativo parte da 0, un valore che non può essere il numero segreto: così il ciclo while parte sicuramente. Se cerchi a metà dell'intervallo (10, poi 5 o 15, ...) bastano al massimo 5 tentativi.
Esercizio 3 · Conto della spesa. Scrivi un programma guidato da menu con queste funzioni:
stampa_menu()mostra le voci:1aggiungi un prodotto,2mostra il totale,0esci;prezzo_valido(prezzo)restituisceTruese il prezzo è maggiore di 0;calcola_iva(importo)restituisce l'IVA sull'importo, usando la costante globaleALIQUOTA_IVA = 0.22;main()ripete il menu finché l'utente non sceglie0. Con1chiede il prezzo (e lo richiede finché non è valido) e lo somma al totale; con2mostra imponibile, IVA e totale con due decimali.
Fai partire main con if __name__ == '__main__':.
Soluzione
ALIQUOTA_IVA = 0.22
def stampa_menu():
print('--- CONTO DELLA SPESA ---')
print('1 - Aggiungi un prodotto')
print('2 - Mostra il totale')
print('0 - Esci')
def prezzo_valido(prezzo):
return prezzo > 0
def calcola_iva(importo):
return importo * ALIQUOTA_IVA
def main():
imponibile = 0
scelta = -1
while scelta != 0:
stampa_menu()
scelta = int(input('Scelta: '))
if scelta == 1:
prezzo = float(input('Prezzo del prodotto: '))
while not prezzo_valido(prezzo):
print('ERRORE: il prezzo deve essere maggiore di 0.')
prezzo = float(input('Prezzo del prodotto: '))
imponibile += prezzo
elif scelta == 2:
iva = calcola_iva(imponibile)
print(f'Imponibile: {imponibile:.2f} euro')
print(f'IVA: {iva:.2f} euro')
print(f'Totale: {imponibile + iva:.2f} euro')
elif scelta == 0:
print('Arrivederci!')
else:
print('Scelta non valida.')
if __name__ == '__main__':
main()
Prova con 1, 10; 1, -5 (rifiutato), 15; poi 2 e 0. Imponibile 25.00, IVA 5.50, totale 30.50 euro. L'accumulatore imponibile è una variabile locale di main: non serve nessuna variabile globale. All'esame troverai la stessa struttura, con le funzioni in un modulo separato da main.py.