Created on 13 Sep 2013 ; Modified on 29 Sep 2016
Questa nota deriva da questo chiaro e conciso articolo pubblicato anni fa nel blog SaltyCrane Blob
In python è possibile definire funzioni con un numero variabile di argomenti utilizzando le
sintassi *args
e **kwargs
.
Ad esempio, eseguento nell'interprete python:
>>> def tf(*args):
... print type(args), args
>>> tf(1, 2, 3, 4)
<type 'tuple'> (1, 2, 3, 4)
>>> tf('a', 'b')
<type 'tuple'> ('a', 'b')
Abbiamo definito la funzione tf
che ci dice (print
) il tipo della struttura dati args
e il suo valore.
La prima chiama a tf
ha 4 parametri. La seconda ne ha 2. Come si vede python non
fa una piega e le esegue entrambe. args
è una tupla. Quindi potremo accedere agli argomenti
tramite posizione. Ad esempio args[0]
ci restituirà il primo argomento. Mentre len(args)
ci indica con quanti argomenti è stata chiamata la funzione.
Se si utilizza **wkargs
potremmo avere:
>>> def tf2(**kwargs):
... print type(kwargs), kwargs
>>> tf2(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: tf2() takes exactly 0 arguments (2 given)
>>> tf2(uno=1, due=2)
<type 'dict'> {'due': 2, 'uno': 1}
>>> tf2(uno=1, due=2, altro='roma')
<type 'dict'> {'altro': 'roma', 'due': 2, 'uno': 1}
In questo caso abbiamo definito la funzione tf2
. Anche questa ci indica il tipo della struttura
dati accettata dalla funzione (kwargs
) e il suo valore.
Quando proviamo a chiamare tf2
con una serie di parametri posizionali, otteniamo un errore (TypeError
).
Errore che personalmente trovo un po' fuorviante. Infatti il problema non consiste nel numero di parametri usati,
ma nel loro tipo.
Chiamando tf2
utilizzando parametri con nome, ecco che il tutto prende vita. Qui vediamo che la struttura dati
kwargs
è un dizionario. Quindi l'acceso agli elementi avviene tramite chiave. Ad esempio kwargs['uno']
ci
restituirà il valore 1
. Mentre la quantità di argomenti la possiamo conoscere, come nel caso precedente,
utilizzando len(kwargs)
.
E' possibile utilizzare entrambe le strutture dati contemporaneamente. A patto di usare prima args
e poi kwargs
.
Ad esempio:
>>> def tf3(*args, **kwargs):
... print type(args), args
... print type(kwargs), kwargs
...
>>> tf3()
<type 'tuple'> ()
<type 'dict'> {}
>>> tf3(1, due=2)
<type 'tuple'> (1,)
<type 'dict'> {'due': 2}
>>> tf3(1,2,tre=3,quattro=4)
<type 'tuple'> (1, 2)
<type 'dict'> {'quattro': 4, 'tre': 3}
>>> tf3(uno=1)
<type 'tuple'> ()
<type 'dict'> {'uno': 1}
>>> tf3(1)
<type 'tuple'> (1,)
<type 'dict'> {}
>>> tf3(uno=1, 2)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
tf3
può essere chiamata con un numero variabile di parametri posizionali e parametri con nome. Ma i parametri posizionali,
se presenti, devono precedere tutti i parametri con nome. Come ci mette bene in evidenza l'ultima chiamata a tf3
in cui abbiamo osato scrivere prima un parametro con nome e poi un posizionale.
Bene. Fin qui abbiamo visto l'uso di * e ** nella definizione della funzione.
Ma se li usassimo in fase di chiamata alla funzione? Che accadrebbe? Python lo interpreterebbe come un ordine di usare la struttura dati in modo acconcio per la chiamata in corso. Vediamo questo esempio.
>>> def tf4(uno, due, tre=None, quattro=None):
... print type(uno), uno
... print type(due), due
... print type(tre), tre
... print type(quattro), quattro
...
>>> tf4(1,2)
<type 'int'> 1
<type 'int'> 2
<type 'NoneType'> None
<type 'NoneType'> None
>>> tf4(1,2,3,4)
<type 'int'> 1
<type 'int'> 2
<type 'int'> 3
<type 'int'> 4
>>> tf4(1,2,quattro=4,tre=3)
<type 'int'> 1
<type 'int'> 2
<type 'int'> 3
<type 'int'> 4
>>> p=('uno','due',)
>>> tf4(*p)
<type 'str'> uno
<type 'str'> due
<type 'NoneType'> None
<type 'NoneType'> None
>>> kw={'tre':'tre', 'quattro':'quattro'}
>>> tf4(*p,**kw)
<type 'str'> uno
<type 'str'> due
<type 'str'> tre
<type 'str'> quattro
>>> tf4(**kw)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: tf4() takes at least 2 arguments (2 given)
>>> tf4(**p)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: tf4() argument after ** must be a mapping, not tuple
In questo esempio tf4
è una normale funzione cou un numero fisso (4) di parametri. I primi due
posizionali e i secondi due con nome.
Le chiamate tf4(1,2) ; tf4(1,2,3,4) ; tf4(1,2,quattro=4,tre=3)
, mostrano il comportamento canonico
della funzione.
Dopo di che definiamo la struttura dati p
come una tupla di due elementi e chiamiamo tf4(*p)
. Python
spacchetterà la tupla nei parametri posizionali della funzione ed eseguirà la chiamata. E' come se
avessimo chiamato tf4('uno','due')
. I None
sono i valori di default dei parametri con nome.
Se definiamo il dizionario kw
con le stesse parole chiave dei parametri con nome (tre
e quattro
),
e chiamiamo tf4(*p, **kw)
ecco che python organizza l'esecuzione della funzione sia con i parametri
posizionali contenuti in p
, che con quelli con nome contenuti in kw
.
Le ultime due chiamate dimostrano possibili errori. Nella penultima abbiamo chiesto a python
di eseguire tf4
senza parametri posizionali, che invece sono obbligatori. Nell'ultima
abbiamo sbagliato la tipizzazione della struttura passata. **
attende un dizionario, non una tupla.
E se nel dizionario un nome di parametro fosse errato? O in eccesso? Presto fatto:
>>> kw2={'tre':'tre', 'q':'quattro'}
>>> kw3={'tre':'tre', 'quattro':'quattro', 'cinque':'cinque'}
>>> tf4(*p, **kw2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: tf4() got an unexpected keyword argument 'q'
>>> tf4(*p, **kw3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: tf4() got an unexpected keyword argument 'cinque'
In entrambi i casi Python segnala il fatto che vi è un parametro con nome sconosciuto.