Nella Lezione 18 del mio Tutorial base ho già parlato
del meccanismo dei parametri di default e dei keyword arguments
che permettono di chiamare una funzione omettendo alcuni dei suoi argomenti. Però in Python esistono alcune
funzioni builtin che accettano un qualsiasi numero di argomenti. Un esempio classico è
la print()
:
>>> print() # nessun argomento >>> print("uno")
uno
>>> print("uno", "due", "tre")
uno due tre
>>> print("uno", "due", "tre", "quattro", "cinque", "sei", "sette", "otto", "nove", "dieci")
uno due tre quattro cinque sei sette otto nove dieci
Come è possibile definire una funzione che abbia un simile comportamento? Attenzione a non confondersi: in Python
esistono anche funzioni, come la sum()
, che accettano come argomento una lista ed
operano sui suoi elementi. Quello che noi vorremmo fare, però, è diverso: passare non un solo
parametro contenente tanti elementi (cioè quello che nella terminologia di Python viene chiamato un
iterable), ma direttamente gli elementi stessi sui quali la funzione deve operare. Chiariamoci le idee con questo
esempio:
>>> sum([1, 2, 3, 4]) # passo UNA lista contenente 4 elementi
10
>>> sum(1, 2, 3, 4) # questo non si puo'fare!
Traceback (most recent call last): File "
", line 1, in sum(1, 2, 3, 4) TypeError: sum() takes at most 2 arguments (4 given)
Supponiamo di aver definito una funzione in questo modo:
>>> def func(arg1, arg2, arg3, arg4):
. . .
arg1
, arg2
, arg3
e arg4
sono detti parametri formali
della funzione. Quando chiameremo la funzione nel corso del programma principale Python sostituirà ad essi i parametri
attuali indicati nella chiamata ed eseguirà il corpo della funzione (le righe indentate dopo la def
). Per chiamare
la nostra funzione potremmo scrivere, per esempio:
>>> func(1, 5, 6, 10)
Gli argomenti elencati in questo modo (nella chiamata) sono detti positional arguments e vengono
assegnati ai parametri formali in base al loro ordine (quindi eseguendo la funzione arg1
assumerà
il valore 1, arg2
il valore 5 eccetera).
Come saprete Python ci permette anche di indicare esplicitamente, nella chiamata, il nome degli argomenti, così:
>>> func(arg1=1, arg2=5, arg3=6, arg4=10)
Gli argomenti indicati in questo modo sono detti keyword arguments e sono assegnati ai parametri formali in base al nome fornito. Per i keyword arguments non è necessario rispettare l'ordine che abbiamo stabilito della definizione, in quanto diciamo noi esplicitamente a Python come deve associare gli argomenti. (ATTENZIONE! Non confondete i keyword arguments con i parametri di default, che sono invece elencati nella definizione, così:
>>> def func(arg1, arg2, arg3, arg4=10):
>>> # arg4 e' un argomento di default, che potra' essere presente o meno nella chiamata
. . .
Python ci permette di mescolare liberamente, nella chiamata, argomenti positional e keyword, con l'unica restrizione che i keyword devono sempre seguire i positional. Per esempio:
>>> func(1, 5, 6, 10) # tutti positional, assegnati nel loro ordine
>>> func(1, 5, arg4=10, arg3=6) # due positional e due keyword (non in ordine)
>>> func(arg3=6, arg4=10, arg1=1, arg2=5) # tutti keyword
>>> func(arg1=1, arg2=5, 6, 10) # errore: i positional devono precedere i keyword
Per una trattazione più approfondita vedi la mia Lezione 18.
Apriamo IDLE e scriviamo:
>>> def func(*args):
print(args)
>>>
(vi ricordo che in IDLE, quando definiamo una funzione, Python interpreta le linee di codice successive come il suo corpo. Per interrompere la definizione e tornare al prompt dovete premere due volte <RETURN>). Il codice che abbiamo scritto viene accettato senza errori, ma qual è l'effetto di questa sintassi? Proviamo a chiamare la funzione che abbiamo definito:
>>> func(1)
(1, )
>>> func(1, 2)
(1, 2)
>>> func(1, 2, 3)
(1, 2, 3)
Vediamo che ora la nostra funzione accetta un numero variabile di argomenti: quando la funzione viene
chiamata il suo parametro formale args
diventa una tuple
, che contiene tutti gli argomenti che
abbiamo passato alla funzione (nella prima chiamata la func()
stampa una tuple
con un solo
elemento, che viene scritta (1, )
per distinguerla da un numero tra parentesi).
Ora possiamo scrivere una funzione simile alla sum()
, che accetta un numero variabile di argomenti e restituisce
la loro somma:
>>> def somma(*args): s = 0 # comincio con 0 for i in args: s += i # poi sommo tutti i numeri in args return s >>> somma(1)
1
>>> somma(1, 2)
3
>>> somma(1, 2, 3, 4)
10
Vediamo di capire meglio la sintassi che abbiamo usato:
L'operatore *
scritto davanti al nome di una variabile è detto unpacking operator:
esso ha l'effetto di trasformare la variabile alla quale è applicato in un iterable
, al quale può essere
assegnato un numero indefinito di valori.
In particolare questo operatore è impiegato nella lista dei parametri formali quando definiamo una funzione; in questo caso notiamo che:
args
è il nome di una variabile (e quindi si potrebbe usare al suo posto qualsiasi altro nome
di variabile), però è diventata di uso comune come abbreviazione di arguments;args
diventerà nel corpo della funzione una tuple
alla
quale sarà assegnato un numero variabile di positional arguments;Facciamo qualche esempio per chiarire gli ultimi due punti, un po' difficili da comprendere a fondo:
>>> def func(arg1, *args): # arg1 e' obbligatorio, args raccoglie tutti gli altri positional print("arg1 =", arg1, "args =", args) >>> func(1)
arg1 = 1 args = ()
>>> func(1, 2)
arg1 = 1 args = (2,)
>>> func(1, 2, 3, 4, 5)
arg1 = 1 args = (2, 3, 4, 5)
>>> func(arg1=1)
arg1 = 1 args = ()
La nostra func()
accetta un argomento arg1
obbligatorio, e poi un numero variabile di
argomenti posizionali. Quando la chiamiamo il primo argomento che forniamo viene assegnato ad arg1
e tutti
i successivi vanno in args
. Possiamo anche chiamare arg1
come keyword
a patto di non mettere altri argomenti (perchè nella chiamate i keyword arguments devono seguire
i positional). Ora provate questa:
>>> def func(*args, arg1): # args raccoglie tutti i positional, arg1 deve essere keyword print("args =", args, "arg1 =", arg1) >>> func(1, 2, arg1=3) # ok
args = (1, 2) arg1 = 3
>>> func(arg1=3) # nessun positional
args = () arg1 = 3
>>> func(1) # errore: arg1 e' obbligatorio
Traceback (most recent call last): File "
", line 1, in func(1) TypeError: func() missing 1 required keyword-only argument: 'arg1'
Ora abbiamo dichiarato un argomento arg1
dopo *args
. Quando chiamiamo
la funzione arg1
deve essere obbligatoriamente un keyword, mentre
tutti i positional vengono assegnati ad args
(notate che nel messaggio di errore
Python ci avvisa che manca un keyword).
massimo
che accetti almeno un argomento e restituisca
il massimo tra tutti i suoi argomenti.massimo1
che accetti un numero variabile di argomenti (anche nessuno) e
restituisca 0 se chiamata senza argomenti, oppure il massimo tra i suoi argomenti.divisibile
che accetti un numero variabile di positional,
più un keyword div
, e restituisca True
se tutti gli argomenti sono divisibili
per div
, False
altrimenti (se chiamata con 0 positional deve restituire
True
).Oltre ad *args
Python ammette anche una sintassi specifica per i keyword
arguments. Definiamo in IDLE quest'altra funzione:
>>> def func(**kwargs): print(kwargs) >>> func(arg1=1)
{'arg1': 1}
>>> func(1)
Traceback (most recent call last): File "
", line 1, in func(1) TypeError: func() takes 0 positional arguments but 1 was given
L'operatore **
scritto davanti al nome di una variabile ha l'effetto di trasformare
la variabile alla quale è applicato in un dizionario
, al quale può essere assegnato un numero
indefinito di coppie chiave-valore.
Quando questo operatore viene impiegato nella lista dei parametri formali nella definizione di una funzione essa
accetterà un numero variabile di keyword arguments, che saranno assegnati alle coppie
chiave-valore del dizionario kwargs
.
Facciamo anche qui qualche osservazione:
kwargs
è il nome di una variabile, di uso comune come abbreviazione di
keyword arguments;kwargs
diventerà un dizionario: i nomi degli argomenti diventeranno
le chiavi mentre i valori saranno assegnati ai corrispondenti valori del dizionario;**kwargs
è l'ultimo parametro.>>> def func(arg1, **kwargs): # un positional, poi un numero variabile di keyword print("arg1 =", arg1, "kwargs =", kwargs) >>> func(1, b=2, c=3)
arg1 = 1 kwargs = {'b': 2, 'c': 3}
>>> def func(*args, **kwargs): # positional e keyword in numero variabile print("args =", args, "kwargs =", kwargs) >>> func(1, 2, 3, d=4)
args = (1, 2, 3) kwargs = {'d': 4}
Bisogna fare un'osservazione importante: quando definiamo una funzione dobbiamo indicare nella
def
i nomi dei suoi parametri formali; nella chiamata Python controlla che i nomi dei nostri
keyword arguments siano effettivamente presenti tra i nomi dei parametri formali, e se
abbiamo sbagliato si ferma con un errore. Ad esempio:
>>> def area(base, altezza): return base * altezza >>> area(base=5, altezz= 10)
Traceback (most recent call last): File "
", line 1, in area(base=5, altezz=10) TypeError: area() got an unexpected keyword argument 'altezz'
Qui ho volutamente commesso un errore scrivendo altezz
, ma Python si è accorto che questo nome non è
un parametro formale. Se invece usiamo **kwargs
la nostra funzione accetterà qualsiasi
nome di argomento, e sarà responsabilità di chi scrive la funzione controllare che i nomi siano corretti. Questo
potrebbe complicare notevolmente il corpo della funzione. Vediamo la stessa funzione area
definita in
questo modo:
>>> def area(**kwargs): if "base" in kwargs.keys() and \ "altezza" in kwargs.keys() and \ len(kwargs) == 2: return kwargs["base"] * kwargs["altezza"] else: raise TypeError("Inappropriate argument type") >>> area(altezza=5, base=8)
40
area(alt=5, base=8)
>>> Traceback (most recent call last): File "
", line 1, in area(alt=5, base=8) File " ", line 7, in area raise TypeError("Inappropriate argument type") TypeError: Inappropriate argument type
Le righe #2-#4 servono a controllare che i parametri inseriti siano
esatti: nella #2 controlliamo se base
è contenuto nelle chiavi di kwargs
,
nella #3 controlliamo se c'è altezza
e nella #4 controlliamo
di non aver inserito nessun altro nome. A questo punto se tutto è corretto calcoliamo l'area, altrimenti(nella
#7) solleviamo un'eccezione. Per queste difficoltà l'uso di **kwargs
è di solito
limitato a funzioni che accettano un gran numero di parametri, evitando così di elencarli tutti nella definizione, ma è
controproducente in funzioni abbastanza semplici.
provaparametri1
che accetti un solo parametro **kwargs
e che
stampi i nomi dei keyword arguments passati nella chiamata (se non ricordate
come si trovano le chiavi di un dizionario guardate qui).provaparametri2
che accetti un solo parametro **kwargs
e che
stampi le coppie nome - valore dei keyword arguments passati nella chiamata
(guardate sempre qui).provaparametri3
che accetti un solo parametro **kwargs
. La funzione
deve verificare che i nomi dei keyword arguments passati nella chiamata siano compresi tra par1
par2 par3 par4
e restituire True
se questo è vero, False
altrimenti.in
ed una lista.Facciamo per ultima cosa notare che l'uso dell'unpacking operator *
non è limitato
alla lista dei parametri formali nella dichiarazione di una funzione, ma esso può essere usato anche nelle normali istruzioni
di assegnazione. Python richiede però che esso faccia parte di una tuple (cioè che sia in un'istruzione di assegnazione
multipla).
>>> a, *args = 1, 2, 3, 4 >>> a
1
>>> args
[2, 3, 4]
>>> *args = 1, 2, 3
SyntaxError: starred assignment target must be in a list or tuple
Fine della lezione