15 Funzioni
La maggior parte delle istruzioni in un tipico programma Python fa parte di una funzione. Inserire il codice nelle funzioni può migliorare la velocità di esecuzione rispetto al codice a livello di modulo. Ci sono ottime ragioni pratiche per inserire la maggior parte del codice nelle funzioni, e non ci sono svantaggi: chiarezza, leggibilità e riutilizzabilità del codice migliorano quando si evitano blocchi sostanziali di codice a livello di modulo.
Una funzione è un gruppo di istruzioni eseguite su richiesta. Python fornisce molte funzioni predefinite e consente ai programmatori di definire le proprie. Una richiesta di eseguire una funzione è nota come chiamata di funzione. Quando chiamiamo una funzione, possiamo passare argomenti che specificano i dati su cui la funzione eseguirà il calcolo. In Python, una funzione restituisce sempre un risultato: o None
o un valore, risultato del calcolo. Le funzioni definite all’interno delle dichiarazioni di classe sono note anche come metodi.
Python è alquanto insolito nella flessibilità che offre al programmatore nel definire e chiamare funzioni. Questa flessibilità significa che alcuni vincoli non sono espressi adeguatamente solo dalla sintassi. In Python, le funzioni sono oggetti (valori), gestiti proprio come altri oggetti. Quindi, possiamo passare una funzione come argomento in una chiamata a un’altra funzione, e una funzione può restituire un’altra funzione come risultato di una chiamata. Una funzione, proprio come qualsiasi altro oggetto, può essere associata a una variabile, può essere un elemento in un contenitore e può essere un attributo di un oggetto. Le funzioni possono anche essere chiavi in un dizionario. Il fatto che le funzioni siano oggetti ordinari in Python è spesso espresso dicendo che le funzioni sono oggetti di prima classe.
Esempio di funzione come oggetto di prima classe:
1from math import sin, cos, tan, asin, acos, atan, log, exp
2def aggiungi_inverse(dizionario_inverse, funzione, inverse_funzione):
3 if not callable(funzione) or not callable(inverse_funzione):
4 raise ValueError("Entrambi i parametri devono essere funzioni")
5 if funzione not in dizionario_inverse and inverse_funzione not in dizionario_inverse.values():
6 dizionario_inverse[funzione] = inverse_funzione
7 dizionario_inverse[inverse_funzione] = funzione
8math_map = {sin: asin, cos: acos, tan: atan, log: exp}
9aggiungi_inverse(math_map, exp, log)
10aggiungi_inverse(math_map, asin, sin)
import pprint
11pprint.pprint(math_map)
- 1
-
Import delle funzioni matematiche dal modulo
math
. - 2
-
La funzione
aggiungi_inverse
accetta un dizionario e due funzioni. - 3
-
Controlla che i parametri siano funzioni: Verifica che i parametri siano funzioni utilizzando
callable
. - 4
- Errore se i parametri non sono funzioni: Solleva un’eccezione se uno dei parametri non è una funzione.
- 5
- Controlla se la funzione non è già una chiave e se l’inversa non è già un valore nel dizionario: Verifica che la funzione non sia già presente come chiave e che l’inversa non sia già presente come valore nel dizionario.
- 6
- Aggiunge la funzione al dizionario con la sua inversa come valore.
- 7
- Aggiunge l’inversa della funzione al dizionario con la funzione originale come valore.
- 8
- Creazione di un dizionario iniziale con alcune funzioni matematiche e le loro inverse.
- 9
-
Chiamata alla funzione
aggiungi_inverse
con nuove funzioni da aggiungere. - 10
- Aggiunta di un’altra coppia funzione-inversa.
- 11
- Stampa del dizionario per verificare che le nuove coppie siano state aggiunte correttamente:
{<built-in function cos>: <built-in function acos>,
<built-in function log>: <built-in function exp>,
<built-in function sin>: <built-in function asin>,
<built-in function tan>: <built-in function atan>}
In questo modo, la funzione aggiungi_inverse
aggiunge solo le coppie funzione-inversa che non sono già presenti nel dizionario, evitando duplicazioni o errori.
Il dizionario math_map
diventa bidirezionale, mappando ogni funzione alla sua inversa e viceversa. Notiamo che in questo caso la funzione muta il suo argomento. In Python, la convenzione usuale è che tali funzioni non restituiscano un valore.
15.1 Definizione delle funzioni
L’istruzione def
è il modo usuale per creare una funzione. def
è una dichiarazione composta a singola clausola con la seguente sintassi:
- 1
-
nome_funzione
è un identificatore. - 2
- Le istruzioni indentate costituiscono il corpo della funzione.
Quando l’interprete incontra una definizione def
, compila il corpo della funzione, creando un oggetto funzione, e associa nome_funzione
all’oggetto funzione compilato nello spazio dei nomi contenitore (tipicamente lo spazio dei nomi del modulo, o uno spazio dei nomi di classe quando si definiscono metodi).
Esempio di funzione senza parametri:
- 1
-
Definizione della funzione
saluta
. - 2
-
Stampa
Ciao!
. - 3
-
Chiamata alla funzione
saluta
.
Esempio di funzione con un parametro:
- 1
-
Definizione della funzione
raddoppia
con un parametrox
. - 2
-
Restituisce il valore di
x
moltiplicato per2
. - 3
-
Chiamata a
raddoppia
con l’argomento5
. Output:10
. - 4
-
Chiamata a
raddoppia
con l’argomento'ciao'
. Output:ciaociao
.
15.2 Parametri
I parametri (anche detti parametri formali) nominano i valori passati in una chiamata di funzione e possono specificare valori predefiniti. Ogni volta che chiamiamo una funzione, la chiamata associa ogni nome di parametro al valore corrispondente (argomento) in un nuovo spazio dei nomi locale. Questo spazio dei nomi locale esiste solo per la durata della funzione, e Python lo distrugge al termine dell’esecuzione della funzione. È importante notare che gli argomenti sono oggetti e che le modifiche a questi oggetti mutabili (come liste o dizionari) all’interno della funzione sono visibili anche al di fuori della funzione, nell’ambito del chiamante. Tuttavia, gli identificatori dei parametri non sono accessibili fuori dalla funzione.
Ecco un esempio illustrativo che mostra come i parametri di una funzione si comportano in Python, specialmente quando si tratta di oggetti mutabili:
1def modifica_lista(lista):
2 lista.append(4)
3 lista = [1, 2, 3]
4 print("Dentro la funzione:", lista)
5mia_lista = [1, 2]
6modifica_lista(mia_lista)
7print("Fuori dalla funzione:", mia_lista)
- 1
-
La funzione prende un parametro
lista
, che è un riferimento a una lista passata come argomento. - 2
-
La funzione modifica la lista originale aggiungendo un nuovo elemento
4
. Questa modifica sarà visibile anche al di fuori della funzione perchélista
è un riferimento all’oggetto lista originale. - 3
-
lista
viene riassegnata a una nuova lista[1, 2, 3]
. Questa riassegnazione non modifica l’oggetto originale passato come argomento, ma crea un nuovo oggetto locale a cuilista
ora punta. Questa modifica non sarà visibile al di fuori della funzione. - 4
-
Stampa il contenuto dell’identificatore
lista
all’interno della funzione, che ora è[1, 2, 3]
a causa della riassegnazione. - 5
-
Creiamo una lista chiamata
mia_lista
con i valori[1, 2]
. - 6
-
Passiamo
mia_lista
alla funzionemodifica_lista
. - 7
-
Stampa il contenuto di
mia_lista
dopo la chiamata alla funzione, mostrando che l’elemento4
è stato aggiunto, ma la riassegnazione a[1, 2, 3]
non ha avuto effetto:
Questo esempio mostra come l’oggetto mutabile (la lista) può essere modificato all’interno della funzione, e tali modifiche sono visibili al di fuori della funzione. Tuttavia, l’identificatore del parametro (in questo caso lista
) viene distrutto dopo l’uscita dalla funzione e non è accessibile al di fuori della funzione.
15.2.1 Parametri posizionali
Un parametro posizionale è un identificatore, che nomina il parametro. Utilizziamo questi nomi all’interno del corpo della funzione per accedere ai valori degli argomenti della chiamata.
Esempio di funzione con parametri posizionali:
- 1
-
Definizione della funzione
somma
con i parametria
eb
. - 2
-
Restituisce la somma di
a
eb
. - 3
-
Chiamata a
somma
con gli argomenti3
e5
. Output:8
.
15.2.2 Parametri nominati
I parametri nominati normalmente assumono la forma nome=espressione
. Questi parametri agiscono come valori predefiniti valutati una sola volta dall’interprete.
Esempio di funzione con parametri nominati:
- 1
-
Definizione della funzione
saluta
con il parametro nominatonome
e un valore predefinito. - 2
- Stampa un messaggio di saluto.
- 3
-
Chiamata a
saluta
senza argomenti. Output:Ciao, ospite!
. - 4
-
Chiamata a
saluta
con l’argomento “Maria”. Output:Ciao, Maria!
.
Ecco un esempio con un parametro che utilizza un’espressione come valore predefinito:
1from datetime import datetime
2def saluta(nome="ospite", momento=datetime.now().strftime("%H:%M")):
3 print(f"Ciao, {nome}! L'ora attuale è {momento}.")
4saluta()
5saluta("Maria")
6saluta("Luigi", "14:30")
- 1
-
Importa il modulo
datetime
che sarà usato per ottenere l’ora attuale. - 2
-
Definisce la funzione
saluta
con due parametri nominati:nome
con un valore predefinito diospite
emomento
con un valore predefinito che è l’ora corrente formattata come stringaHH:MM
. - 3
- Stampa un messaggio di saluto che include il nome e il momento specificato.
- 4
-
Chiamata a
saluta
senza argomenti. Utilizza entrambi i valori predefiniti. Output:Ciao, ospite! L'ora attuale è <ora_attuale>.
. - 5
-
Chiamata a
saluta
con l’argomentoMaria
. Utilizza il valore predefinito permomento
. Output:Ciao, Maria! L'ora attuale è <ora_attuale>.
. - 6
-
Chiamata a
saluta
con gli argomentiLuigi
e14:30
. Non utilizza nessun valore predefinito. Output:Ciao, Luigi! L'ora attuale è 14:30.
.
In questo esempio, il parametro momento
utilizza un’espressione come valore predefinito che viene valutata una volta sola al momento della definizione della funzione. Se viene chiamata senza argomenti, la funzione utilizza questi valori predefiniti.
15.2.3 Marcatore di parametri posizionali
La firma di una funzione può contenere un marcatore di parametri posizionali /
come parametro fittizio. I parametri che precedono il marcatore sono noti come parametri posizionali, e devono essere forniti come argomenti posizionali, non come argomenti nominati, quando si chiama la funzione; usare argomenti nominati per questi parametri genera un’eccezione TypeError
.
Ad esempio, il tipo built-in int
ha la seguente firma:
Quando si chiama int
, è necessario passare un valore per x
e questo deve essere passato posizionalmente. base
(usato quando x
è una stringa da convertire in int
) è opzionale e può essere passato sia posizionalmente che come argomento nominato. È un errore passare x
come numero e anche passare base
, ma la notazione non può catturare questa particolarità.
Esempio di utilizzo del marcatore di parametri posizionali:
1def funzione(x, /, base=10):
2 return x + base
3print(funzione(5))
4print(funzione(5, base=2))
try:
5 print(funzione(x=5))
except TypeError as e:
print(e)
- 1
-
Definizione della funzione
funzione
con un parametro posizionalex
e un parametro nominatobase
con un valore predefinito. - 2
-
La funzione ritorna la somma di
x
ebase
. - 3
-
Chiamata a
funzione
con l’argomento posizionale5
. Utilizza il valore predefinito dibase
. Output:15
. - 4
-
Chiamata a
funzione
con l’argomento posizionale5
e l’argomento nominatobase=2
. Output:7
. - 5
-
Chiamata a
funzione
con l’argomento nominatox=5
. Genera unTypeError
perchéx
deve essere fornito come argomento posizionale.
Senza il marcatore posizionale /
:
1def funzione(x, base=10):
2 return x + base
3print(funzione(5))
4print(funzione(5, 2))
5print(funzione(5, base=2))
6print(funzione(x=5))
7print(funzione(x=5, base=2))
- 1
-
Definizione della funzione
funzione
conx
ebase
come argomenti. - 2
-
La funzione ritorna la somma di
x
ebase
. - 3
-
Chiamata a
funzione
con l’argomento posizionale5
. Utilizza il valore predefinito dibase
. Output:15
. - 4
-
Chiamata a
funzione
con gli argomenti posizionali5
e2
. Output:7
. - 5
-
Chiamata a
funzione
con l’argomento posizionale5
e l’argomento nominatobase=2
. Output:7
. - 6
-
Chiamata a
funzione
con l’argomento nominatox=5
. Utilizza il valore predefinito dibase
. Output:15
. - 7
-
Chiamata a
funzione
con gli argomenti nominatix=5
ebase=2
. Output:7
.
15.2.4 Collettori di argomenti
Python consente di raccogliere argomenti che non corrispondono a parametri specifici utilizzando i collettori di argomenti posizionali (*nome
) e nominati (**nome
).
Un collettore di argomenti posizionali crea una tupla, mentre un collettore di argomenti nominati crea un dizionario.
Esempio di funzione con collettore di argomenti posizionali:
1def somma_tutti(*numeri):
2 print(type(numeri))
3 return sum(numeri)
4print(somma_tutti(1, 2, 3, 4))
5print(somma_tutti())
- 1
-
Definizione della funzione
somma_tutti
con il collettore di argomenti*numeri
. - 2
-
Stampa il tipo di
numeri
. - 3
- Restituisce la somma di tutti i numeri.
- 4
-
Chiamata a
somma_tutti
con gli argomenti1
,2
,3
,4
. Output:10
. - 5
-
Chiamata a
somma_tutti
senza argomenti. Output:0
.
Esempio di funzione con collettore di argomenti nominati:
1def stampa_dizionario(**diz):
2 print(type(diz))
3 for chiave, valore in diz.items():
4 print(f"{chiave}: {valore}")
5stampa_dizionario(nome="Mario", eta=30)
6stampa_dizionario(citta="Roma", paese="Italia")
- 1
-
Definizione della funzione
stampa_dizionario
con il collettore di argomenti**diz
. - 2
-
Stampa il tipo di
diz
. - 3
- Itera sugli elementi del dizionario.
- 4
- Stampa ogni coppia chiave-valore.
- 5
-
Chiamata a
stampa_dizionario
con argomenti nominati. Output:nome: Mario
,eta: 30
. - 6
-
Chiamata a
stampa_dizionario
con altri argomenti nominati. Output:citta: Roma
,paese: Italia
.
Eseguendo questi esempi, si può vedere che *numeri
crea una tupla e **diz
crea un dizionario.
È anche possibile combinare i collettori di argomenti posizionali e nominati nella stessa funzione.
Esempio di funzione con collettore di argomenti posizionali e nominati:
1def mostra_tutti(*args, **kwargs):
2 print("Argomenti posizionali:", args)
3 print("Argomenti nominati:", kwargs)
4mostra_tutti(1, 2, 3, nome="Alice", eta=25)
- 1
-
Definizione della funzione
mostra_tutti
con i collettori di argomenti*args
e**kwargs
. - 2
- Stampa gli argomenti posizionali.
- 3
- Stampa gli argomenti nominati.
- 4
-
Chiamata a
mostra_tutti
con argomenti posizionali e nominati. Output:Argomenti posizionali: (1, 2, 3)
,Argomenti nominati: {'nome': 'Alice', 'eta': 25}
.
15.2.5 Valori predefiniti mutabili
Quando il valore predefinito di un parametro è un oggetto mutabile, e il corpo della funzione altera il parametro, le cose possono complicarsi. Questo perché il valore predefinito viene valutato una sola volta, quando la funzione è definita, non ogni volta che la funzione è chiamata. Di conseguenza, se il valore predefinito viene modificato (come nel caso di una lista alla quale vengono aggiunti elementi), la modifica persiste attraverso le chiamate successive alla funzione.
Esempio di funzione con valore predefinito mutabile:
1def aggiungi_elemento(elemento, lista=[]):
2 lista.append(elemento)
3 return lista
4print(aggiungi_elemento(1))
5print(aggiungi_elemento(2))
- 1
-
Definizione della funzione
aggiungi_elemento
con il parametrolista
avente un valore predefinito di lista vuota. - 2
- Aggiunge l’elemento alla lista.
- 3
- Restituisce la lista.
- 4
-
Chiamata a
aggiungi_elemento
con l’argomento 1. Output:[1]
. - 5
-
Chiamata a
aggiungi_elemento
con l’argomento 2. Output:[1, 2]
.
Nel secondo caso, la lista restituita contiene [1, 2]
anziché [2]
perché il valore predefinito della lista è stato modificato dalla prima chiamata alla funzione e questa modifica persiste nella seconda chiamata. La lista usata come valore predefinito è la stessa in entrambe le chiamate.
Per evitare questo comportamento, è meglio usare None
come valore predefinito e creare una nuova lista all’interno della funzione se None
è passato come argomento.
Esempio di funzione con valore predefinito None
:
1def aggiungi_elemento(elemento, lista=None):
2 if lista is None:
3 lista = []
4 lista.append(elemento)
5 return lista
6print(aggiungi_elemento(1))
7print(aggiungi_elemento(2))
- 1
-
Definizione della funzione
aggiungi_elemento
con il parametrolista
avente un valore predefinito diNone
. - 2
-
Controlla se
lista
èNone
. - 3
-
Se
lista
èNone
, crea una nuova lista vuota. - 4
- Aggiunge l’elemento alla lista.
- 5
- Restituisce la lista.
- 6
-
Chiamata a
aggiungi_elemento
con l’argomento 1. Output:[1]
. - 7
-
Chiamata a
aggiungi_elemento
con l’argomento 2. Output:[2]
.
In questo caso, ogni chiamata alla funzione aggiungi_elemento
crea una nuova lista se None
è passato come valore predefinito, garantendo che ogni chiamata non influenzi le altre.
15.3 Attributi
L’istruzione def
imposta alcuni attributi di un oggetto funzione. Di seguito sono spiegati i principali attributi e come possono essere utilizzati.
15.3.1 __name__
Questo attributo è l’identificatore che def
utilizza come nome della funzione. Puoi riassegnare __name__
a qualsiasi valore stringa, ma se cerchi di dissociarlo, Python genererà un’eccezione TypeError
.
Esempio:
1def saluta():
2 print("Ciao!")
3print(saluta.__name__)
4saluta.__name__ = "greeting"
5print(saluta.__name__)
- 1
-
Definizione della funzione
saluta
. - 2
-
La funzione stampa
"Ciao!"
. - 3
-
Stampa il nome della funzione. Output:
saluta
. - 4
-
Cambia il nome della funzione in
"greeting"
. - 5
-
Stampa il nuovo nome della funzione. Output:
greeting
.
Se provi a dissociare __name__
, otterrai un errore:
15.3.2 __defaults__
Questo attributo contiene una tupla dei valori predefiniti per i parametri nominati della funzione. Puoi riassegnare o dissociare liberamente questo attributo. Se la funzione non ha parametri nominati, __defaults__
sarà una tupla vuota.
Esempio:
1def saluta(nome="ospite"):
2 print(f"Ciao, {nome}!")
3print(saluta.__defaults__)
4saluta.__defaults__ = ("amico",)
5saluta()
- 1
-
Definizione della funzione
saluta
con un parametro nominatonome
avente un valore predefinito. - 2
- La funzione stampa un messaggio di saluto.
- 3
-
Stampa i valori predefiniti dei parametri della funzione. Output:
('ospite',)
. - 4
-
Cambia il valore predefinito del parametro
nome
in"amico"
. - 5
-
Chiama la funzione senza argomenti. Output:
Ciao, amico!
.
Se provi a dissociare __defaults__
, l’attributo sarà semplicemente rimosso:
del saluta.__defaults__
1print(saluta.__defaults__)
- 1
-
Output:
None
.
Questi attributi permettono una flessibilità aggiuntiva nella manipolazione delle funzioni, offrendo modi per ispezionare e modificare il comportamento predefinito di una funzione.
15.3.3 Docstring
Un altro attributo della funzione è la stringa di documentazione, o docstring. Puoi utilizzare o riassegnare la docstring di una funzione come __doc__
. Quando la prima istruzione nel corpo della funzione è un letterale stringa, il compilatore associa quella stringa come attributo docstring della funzione. Una regola simile si applica a classi e moduli. Le docstring possono estendersi su più righe fisiche, quindi è meglio specificarle in forma di stringa tra triple virgolette.
Le docstring sono particolarmente utili perché vengono visualizzate dalla funzione built-in help()
quando viene chiamata con il nome della funzione come argomento. Questo consente di fornire una descrizione dettagliata della funzione, delle sue aspettative di input e del suo comportamento, direttamente all’interno del codice.
Esempio di docstring:
1def somma_sequenza(*numeri):
"""Restituisce la somma di più argomenti numerici.
Gli argomenti sono zero o più numeri.
Il risultato è la loro somma.
2 """
3 return sum(numeri)
4help(somma_sequenza)
- 1
-
Definizione della funzione
somma_sequenza
con il collettore di argomenti*numeri
. - 2
- Docstring della funzione che descrive il suo scopo e gli argomenti.
- 3
- Restituisce la somma degli argomenti.
- 4
-
Chiamata alla funzione
help
con l’argomentosomma_sequenza
, che visualizza la docstring della funzione.
Quando si utilizza la funzione help()
, la docstring viene visualizzata, offrendo informazioni utili sull’utilizzo della funzione:
15.4 Annotazioni
Ogni parametro in una clausola def
può essere annotato con un’espressione arbitraria. Puoi anche annotare il valore di ritorno della funzione utilizzando la forma -> espressione
tra la parentesi )
della clausola def
e i due punti :
che terminano la clausola def
. Queste annotazioni sono memorizzate nell’attributo __annotations__
della funzione e possono essere utilizzate per migliorare la leggibilità del codice e per il type hinting.
Esempio di annotazioni delle funzioni:
- 1
-
Definizione della funzione
f
con annotazioni per i parametria
e il valore di ritorno. - 2
- Corpo della funzione vuoto.
- 3
-
Stampa le annotazioni della funzione
f
. Output:{'a': 'foo', 'return': 'bar'}
.
Il type hinting è una applicazione pratica delle annotazioni, che consente di specificare i tipi di variabili, parametri e valori di ritorno, fornendo informazioni utili per il controllo dei tipi a tempo di esecuzione o per strumenti di analisi statica del codice. Il type hinting può essere particolarmente utile per migliorare la leggibilità del codice e facilitare la rilevazione di errori.
Di seguito, un esempio pratico che utilizza il type hinting:
1def somma_numeri(numeri: list[int]) -> int:
2 """Restituisce la somma di una lista di numeri interi."""
3 return sum(numeri)
4print(somma_numeri([1, 2, 3, 4]))
5print(somma_numeri.__annotations__)
- 1
-
Definizione della funzione
somma_numeri
con annotazioni dei tipi per il parametronumeri
e il valore di ritorno. - 2
- Docstring della funzione che descrive il suo scopo e gli argomenti.
- 3
-
Restituisce la somma degli elementi nella lista
numeri
. - 4
-
Chiamata alla funzione
somma_numeri
con una lista di numeri interi. Output:10
. - 5
-
Stampa le annotazioni della funzione
somma_numeri
. Output:{'numeri': list[int], 'return': <class 'int'>}
.
Possiamo utilizzare annotazioni per funzioni che accettano e restituiscono tipi più complessi, come dizionari o tuple.
Esempio:
1def processa_dati(dati: dict[str, tuple[int, int]]) -> dict[str, int]:
2 """Elabora un dizionario di tuple e restituisce un dizionario di somme."""
3 risultato = {}
4 for chiave, (val1, val2) in dati.items():
5 risultato[chiave] = val1 + val2
6 return risultato
7dati_input = {"a": (1, 2), "b": (3, 4)}
8print(processa_dati(dati_input))
9print(processa_dati.__annotations__)
- 1
-
Definizione della funzione
processa_dati
con annotazioni dei tipi per il parametrodati
e il valore di ritorno. - 2
- Docstring della funzione che descrive il suo scopo e gli argomenti.
- 3
-
Inizializzazione del dizionario
risultato
. - 4
-
Iterazione sugli elementi del dizionario
dati
. - 5
-
Calcola la somma dei valori nella tupla e la assegna al dizionario
risultato
. - 6
-
Restituisce il dizionario
risultato
. - 7
-
Definizione di un dizionario di input
dati_input
. - 8
-
Chiamata alla funzione
processa_dati
con il dizionario di input. Output:{'a': 3, 'b': 7}
. - 9
-
Stampa le annotazioni della funzione
processa_dati
. Output:{'dati': dict[str, tuple[int, int]], 'return': dict[str, int]}
.
15.5 L’Istruzione return
La parola chiave return
può essere utilizzata per terminare una funzione e opzionalmente restituire un valore. Quando una funzione non include una istruzione return
, restituisce implicitamente None
.
Esempio di utilizzo di return
:
- 1
-
Definizione della funzione
quadrato
con il parametrox
. - 2
-
Restituisce il quadrato di
x
. - 3
-
Chiamata alla funzione
quadrato
con l’argomento 5. Output:25
.
Esempio di funzione che restituisce None
:
- 1
-
Definizione della funzione
saluta
. - 2
-
Stampa
"Ciao!"
. - 3
-
Chiamata alla funzione
saluta
e assegnazione del risultato arisultato
. - 4
-
Stampa il valore di
risultato
. Output:Ciao!
seguito daNone
.
15.6 Chiamate
Una chiamata di funzione è un’espressione con la seguente sintassi:
oggetto_funzione
può essere qualsiasi riferimento a un oggetto funzione (o altro oggetto chiamabile); più spesso, è solo il nome della funzione. Le parentesi denotano l’operazione di chiamata della funzione stessa. argomenti
, nel caso più semplice, è una serie di zero o più espressioni separate da virgole ,
, che forniscono i valori per i parametri corrispondenti della funzione.
Esempio di chiamata di funzione:
- 1
-
Definizione della funzione
saluta
con il parametronome
. - 2
- Stampa un messaggio di saluto con il nome.
- 3
-
Chiamata a
saluta
con l’argomento “Luigi”. Output:Ciao, Luigi!
.
15.6.1 Argomenti
Gli argomenti possono essere di due tipi. Gli argomenti posizionali sono semplici espressioni; gli argomenti nominati assumono la forma:
È un errore di sintassi che gli argomenti nominati precedano quelli posizionali in una chiamata di funzione. Zero o più argomenti posizionali possono essere seguiti da zero o più argomenti nominati. Ogni argomento posizionale fornisce il valore per il parametro che corrisponde a esso per posizione (ordine) nella definizione della funzione.
Esempio di funzione con argomenti posizionali e nominati:
- 1
-
Definizione della funzione
f
con parametri posizionali, nominati e un collettore di argomenti. - 2
- Stampa i valori dei parametri e degli argomenti.
- 3
-
Chiamata a
f
con argomenti. Output:1 2 3 4 (5, 6)
.
15.6.2 Associazione degli argomenti ai parametri
Una chiamata deve fornire un argomento per tutti i parametri posizionali obbligatori e può farlo per i parametri nominati che non sono esclusivamente nominati.
La corrispondenza avviene come segue:
I parametri nella forma
*espressione
catturano tutti gli argomenti posizionali rimanenti come una tupla.- 1
- Funzione con tre parametri posizionali di cui uno è il collettore.
- 2
- Chiamata con 5 argomenti posizionali.
I parametri nella forma
**espressione
catturano tutti gli argomenti nominati rimanenti come un dizionario.- 1
- Funzione con due parametri posizionali e un collettore di argomenti nominati.
- 2
- Chiamata con due argomenti posizionali e due argomenti nominati.
Supponiamo che la funzione abbia
N
parametri posizionali e la chiamata abbiaM
argomenti posizionali:Quando
M <= N
, tutti gli argomenti posizionali vengono associati ai primiM
parametri posizionali; i parametri posizionali rimanenti, se presenti, devono essere associati tramite argomenti nominati o avere valori predefiniti.- 1
- Funzione con due parametri posizionali obbligatori e uno opzionale con valore predefinito.
- 2
- Chiamata con due argomenti posizionali.
Quando
M > N
, i rimanenti argomenti posizionali devono essere catturati da*espressione
.- 1
-
Funzione con due parametri posizionali obbligatori e un parametro
*args
per raccogliere argomenti posizionali rimanenti. - 2
- Chiamata con quattro argomenti posizionali.
Quando
M > N
e non c’è un parametro*espressione
, Python solleva un’eccezioneTypeError
.- 1
- Funzione con due parametri posizionali obbligatori.
- 2
-
Chiamata con tre argomenti posizionali, che solleva un
TypeError
. - 3
- Output dell’eccezione.
Gli argomenti nominati vengono quindi associati, nell’ordine delle occorrenze degli argomenti nella chiamata, ai parametri corrispondenti per nome. I tentativi di riassociare un nome di parametro già associato sollevano un’eccezione
TypeError
.1def f(a, b, c): print(a, b, c) 2f(a=1, b=2, c=3) try: 3 f(a=1, b=2, a=3) except TypeError as e: 4 print(e)
- 1
- Funzione con tre parametri posizionali obbligatori.
- 2
- Chiamata con tre argomenti nominati.
- 3
-
Chiamata con un argomento nominato duplicato, che solleva un
TypeError
. - 4
- Output dell’eccezione.
Se a questo punto rimangono argomenti nominati non associati:
Quando la firma della funzione include un parametro
**nome
, l’interprete crea un dizionario con coppie chiave/valore (nome_argomento, valore_argomento) e lo associa anome
nello spazio dei nomi della chiamata alla funzione.- 1
-
Funzione con un parametro posizionale obbligatorio e un parametro
**kwargs
per raccogliere argomenti nominati rimanenti. - 2
- Chiamata con un argomento posizionale e due argomenti nominati.
In assenza di un tale parametro, Python solleva un’eccezione
TypeError
.- 1
- Funzione con un parametro posizionale obbligatorio.
- 2
- Chiamata con un argomento posizionale e uno nominato, che solleva un TypeError.
- 3
- Output dell’eccezione.
I parametri nominati rimanenti non associati vengono associati ai loro valori predefiniti.
- 1
- Funzione con un parametro posizionale obbligatorio e due parametri con valori predefiniti.
- 2
- Chiamata con un argomento posizionale.
- 3
- Chiamata con un argomento posizionale e uno nominato.
A questo punto, lo spazio dei nomi della chiamata alla funzione è completamente popolato e l’interprete esegue il corpo della funzione utilizzando quello spazio dei nomi come spazio dei nomi locale per la funzione.
15.7 Spazi di nomi
I parametri di una funzione, insieme a qualsiasi nome che viene associato (attraverso un’assegnazione o altre istruzioni di binding, come def
) nel corpo della funzione, costituiscono lo spazio dei nomi locale della funzione, noto anche come ambito locale. Ognuna di queste variabili è nota come variabile locale della funzione.
Le variabili che non sono locali sono conosciute come variabili globali (in assenza di definizioni di funzioni annidate, di cui parleremo a breve). Le variabili globali sono attributi dell’oggetto modulo. Ogni volta che una variabile locale di una funzione ha lo stesso nome di una variabile globale, quel nome, all’interno del corpo della funzione, si riferisce alla variabile locale, non a quella globale. Esprimiamo questo dicendo che la variabile locale nasconde la variabile globale con lo stesso nome in tutto il corpo della funzione.
15.7.1 L’istruzione global
Per impostazione predefinita, qualsiasi variabile associata in un corpo di funzione è locale alla funzione. Se una funzione ha bisogno di associare o riassociare alcune variabili globali (pratica non consigliata!), la prima istruzione del corpo della funzione deve essere:
dove identificatori
è uno o più identificatori separati da virgole ,
. Gli identificatori elencati in un’istruzione global
si riferiscono alle variabili globali (cioè attributi dell’oggetto modulo) che la funzione deve associare o riassociare.
Esempio di utilizzo di global
:
1_count = 0
2def contatore():
3 global _count
4 _count += 1
5 return _count
6print(contatore())
7print(contatore())
- 1
-
Variabile globale
_count
inizializzata a 0. - 2
-
Definizione della funzione
contatore
. - 3
-
Dichiarazione di
_count
come variabile globale. - 4
-
Incremento della variabile globale
_count
. - 5
-
Restituzione del valore di
_count
. - 6
-
Chiamata a
contatore
. Output:1
. - 7
-
Chiamata a
contatore
nuovamente. Output:2
.
Senza l’istruzione global
, la funzione contatore
solleverebbe un’eccezione UnboundLocalError
quando viene chiamata, perché _count
sarebbe una variabile locale non inizializzata (non associata). Sebbene l’istruzione global
consenta questo tipo di programmazione, questo stile è inefficace e sconsigliato. Come menzionato in precedenza, quando si desidera raggruppare uno stato e un comportamento, i meccanismi orientati agli oggetti sono generalmente i migliori.
15.7.2 Funzioni annidate e ambiti annidati
Un’istruzione def
all’interno del corpo di una funzione definisce una funzione annidata, e la funzione il cui corpo include la def
è conosciuta come funzione esterna a quella annidata. Il codice nel corpo di una funzione annidata può accedere (ma non riassociare) variabili locali di una funzione esterna, note anche come variabili libere della funzione annidata.
Esempio di funzione annidata che accede a variabili esterne:
1def esterna(a, b, c):
2 def annidata(x):
3 return (x * 100.0) / (a + b + c)
4 print('Percentuali:', annidata(a), annidata(b), annidata(c))
5esterna(10, 20, 30)
- 1
-
Definizione della funzione esterna
esterna
. - 2
-
Definizione della funzione annidata
annidata
. - 3
-
La funzione annidata accede alle variabili
a
,b
ec
della funzione esterna. - 4
- Stampa le percentuali calcolate dalla funzione annidata.
- 5
-
Chiamata alla funzione
esterna
con argomenti10, 20, 30
.
Una funzione annidata che accede ai valori dalle variabili locali delle funzioni contenenti è anche nota come chiusura (closure).
Esempio di chiusura:
1def crea_sommatore(augendo):
2 def somma(addendo):
3 return addendo + augendo
4 return somma
5somma5 = crea_sommatore(5)
6somma9 = crea_sommatore(9)
7print(somma5(10))
8print(somma9(10))
- 1
-
Definizione della funzione esterna
crea_sommatore
. - 2
-
Definizione della funzione annidata
somma
. - 3
-
La funzione
somma
accede alla variabileaugendo
della funzione esterna. - 4
-
La funzione esterna
crea_sommatore
ritorna la funzionesomma
. - 5
-
Creazione di una funzione che somma
5
a un numero. - 6
-
Creazione di una funzione che somma
9
a un numero. - 7
-
Chiamata alla funzione
somma5
con argomento10
. Output:15
. - 8
-
Chiamata alla funzione
somma9
con argomento10
. Output:19
.
La parola chiave nonlocal
funziona in modo simile a global
, ma si riferisce a un nome nello spazio dei nomi di una funzione che circonda lessicamente.
Esempio di nonlocal
:
1def crea_contatore():
2 conteggio = 0
3 def contatore():
4 nonlocal conteggio
5 conteggio += 1
6 return conteggio
7 return contatore
8c1 = crea_contatore()
9c2 = crea_contatore()
10print(c1(), c1(), c1())
11print(c2(), c2())
12print(c1(), c2(), c1())
- 1
-
Definizione della funzione
crea_contatore
. - 2
-
Inizializzazione della variabile
conteggio
. - 3
-
Definizione della funzione annidata
contatore
. - 4
-
Dichiarazione di
conteggio
comenonlocal
. - 5
-
Incremento della variabile
conteggio
. - 6
-
Restituzione del valore di
conteggio
. - 7
-
Restituzione della funzione
contatore
. - 8
-
Creazione di un contatore
c1
. - 9
-
Creazione di un contatore
c2
. - 10
-
Chiamata a
c1
tre volte. Output:1 2 3
. - 11
-
Chiamata a
c2
due volte. Output:1 2
. - 12
-
Chiamata a
c1
ec2
alternata. Output:4 3 5
.
15.8 Espressioni lambda
Se il corpo di una funzione è una singola espressione return
, possiamo (opzionalmente) scegliere di sostituire la funzione con la speciale forma di espressione lambda
:
Un’espressione lambda
è l’equivalente anonimo di una funzione normale il cui corpo è una singola istruzione return
. La sintassi lambda
non utilizza la parola chiave return
. Possiamo usare un’espressione lambda
ovunque potremmo usare un riferimento a una funzione.
Esempio di utilizzo di una lambda
:
- 1
- Lista di numeri.
- 2
-
Utilizzo di una
lambda
per ordinare la lista. - 3
-
Output:
[2, 3, 5, 8]
.
Le espressioni lambda
possono essere utili quando desideriamo utilizzare una funzione estremamente semplice come argomento o valore di ritorno.
Altro esempio:
1calcola_area = lambda lunghezza, larghezza: lunghezza * larghezza
2area1 = calcola_area(5, 10)
3area2 = calcola_area(7, 3)
4print(f"L'area del rettangolo 1 è: {area1}")
5print(f"L'area del rettangolo 2 è: {area2}")
- 1
-
Definisce una lambda che prende due parametri,
lunghezza
elarghezza
, e ritorna il loro prodotto, che rappresenta l’area del rettangolo. - 2
- Utilizza la lambda per calcolare l’area di un rettangolo con lunghezza 5 e larghezza 10.
- 3
- Utilizza la lambda per calcolare l’area di un rettangolo con lunghezza 7 e larghezza 3.
- 4
-
Stampa l’area del primo rettangolo:
"L'area del rettangolo 1 è: 50"
. - 5
-
Stampa l’area del secondo rettangolo:
"L'area del rettangolo 2 è: 21"
.
Mentre lambda
può essere utile, def
è solitamente preferibile: è più generale e ti aiuta a rendere il codice più leggibile, poiché si può scegliere un nome chiaro per la funzione.