Vai al contenuto

Capitolo 5 · Funzioni

Cosa impari

  • a dividere un programma in funzioni, a definirle con def e a chiamarle;
  • a organizzare un programma con la funzione main e 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 random e math, e a scrivere moduli tuoi;
  • a far partire main solo quando serve, con if __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 nome_funzione():
    istruzione
    istruzione
  • 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 def ai 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:

  1. salta alla funzione;
  2. esegue le istruzioni del suo blocco;
  3. torna al punto del programma da cui era partito (il chiamante) e continua da lì.
def saluta():
    print("Ciao!")
    print("Benvenuto!")

print("Inizio")
saluta()
print("Fine")

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:

  1. una variabile locale deve essere creata prima di essere usata;
  2. 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:

import nome_modulo

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.

numero  =  random.randint(1, 100)
  |          |       |       |
variabile  modulo funzione argomenti
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.

import random

random.seed(10)
print(random.randint(1, 100))   # 74
print(random.randint(1, 100))   # 5

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:

return espressione

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:

  1. assegnarlo a una variabile, per usarlo dopo: x = somma(3, 5);
  2. usarlo in un'espressione: tot = somma(a, b) * 2;
  3. 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.

# Modulo: solo definizioni di funzioni
def area_cerchio(raggio):
    return 3.14 * raggio ** 2

def perimetro(raggio):
    return 2 * 3.14 * raggio
import calcoli

r = 5
a = calcoli.area_cerchio(r)
p = calcoli.perimetro(r)
print(a, p)

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:

  1. mostra il menu con una serie di print;
  2. legge la scelta con input;
  3. esegue l'operazione con una struttura if/elif;
  4. 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'
print('Questo file si chiama:', __name__)

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 import di questo file, la condizione è falsa: main() non parte, ma l'altro programma può usare area_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.

# Scrivi qui la tua soluzione
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!').

# Scrivi qui la tua soluzione
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: 1 aggiungi un prodotto, 2 mostra il totale, 0 esci;
  • prezzo_valido(prezzo) restituisce True se il prezzo è maggiore di 0;
  • calcola_iva(importo) restituisce l'IVA sull'importo, usando la costante globale ALIQUOTA_IVA = 0.22;
  • main() ripete il menu finché l'utente non sceglie 0. Con 1 chiede il prezzo (e lo richiede finché non è valido) e lo somma al totale; con 2 mostra imponibile, IVA e totale con due decimali.

Fai partire main con if __name__ == '__main__':.

# Scrivi qui la tua soluzione
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.

Verifica