Argomenti di funzione — nomi, predefiniti e stelle
Passare argomenti per nome, valori predefiniti e la regola sul loro ordine, *args e **kwargs, e scompattare una lista o un dizionario in una chiamata.
Come vuoi imparare?
Il problema che stiamo risolvendo
Le funzioni del capitolo precedente potevano essere chiamate in un solo modo: tanti argomenti quanti i parametri, esattamente in quell'ordine.
In pratica non basta. Considera una funzione per un ordine:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3La seconda riga non ha sollevato alcun errore — ha semplicemente dato una risposta priva di senso. Python non controlla i tipi degli argomenti: qui una stringa e un numero hanno scambiato posto e, poiché un f-string può stampare entrambi, nulla lo ha impedito. Python non ha modo di sapere che l'ordine era invertito.
E se quasi ogni ordine è per un articolo solo? Dovresti digitare 1 ogni volta. Se una funzione ha quattro impostazioni opzionali? Dovresti passarle tutte e quattro ogni volta, incluse quelle che non stai cambiando.
Questo capitolo copre il resto dei modi per passare argomenti: per nome, con valori predefiniti e in numero qualsiasi.
Alla fine di questo capitolo sarai in grado di
- Passare argomenti per nome, e dire quando dovresti
- Dare valori predefiniti, e seguire la regola sul loro ordine
- Rendere alcuni parametri solo per parola chiave, così possono essere passati solo per nome
- Prendere un numero qualsiasi di argomenti con
*args - Prendere argomenti di qualsiasi nome con
**kwargs - Scompattare una lista o un dizionario in una chiamata
Prerequisiti: Funzioni — una decisione, un nome.
Passare per nome
Invece di ricordare l'ordine, scrivi il nome:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penCon i nomi, l'ordine smette di importare — quantity è stato scritto per primo ed è comunque finito nel posto giusto.
Quando dovresti? Quando l'argomento non si spiega da solo. order("pen", 3) si legge bene. Ma send(True, False) non ti dice nulla, mentre send(retry=True, verbose=False) sì. Come regola: dai loro un nome quando passi True/False o un numero nudo.
Valori predefiniti
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))
print(order("pen", quantity=5))1 x pen
5 x pen
5 x penquantity=1 dice "assumi uno se nessuno specifica altrimenti". Il caso comune diventa breve, e quello raro resta esprimibile.
Una regola va obbedita: i parametri con valori predefiniti vanno sempre per ultimi.
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultLa ragione è chiara appena ci pensi: dato order("pen"), come farebbe Python a sapere per quale parametro è "pen"? Con quelli richiesti per primi, non c'è ambiguità.
Un numero qualsiasi di argomenti — *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0La stella in *prices dice "raccogli qui i rimanenti argomenti posizionali". Non serve decidere in anticipo quanti ne arriveranno, e zero va benissimo.
Cos'è la cosa raccolta?
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')Una tupla — la tupla del capitolo quattordici. Il ragionamento è pulito: quanti argomenti ci sono è stabilito nel momento della chiamata e non dovrebbe cambiare dopo, quindi un contenitore immutabile calza a pennello.
Il nomepricesnon è obbligatorio. Il nome convenzionale è*args, che è ciò che vedrai nella maggior parte del codice. Ma un nome significativo è meglio dove esiste —*pricesdice a chi legge cosa sta arrivando,*argsno.
Argomenti di qualsiasi nome — **kwargs
def describe(**fields):
print(type(fields))
for key, value in fields.items():
print(f"{key}: {value}")
describe(item="pen", quantity=3)<class 'dict'>
item: pen
quantity: 3Due stelle raccolgono gli argomenti passati per nome, e il risultato è un dizionario — coppie di nome e valore, che è esattamente ciò che dev'essere.
Una stella dà una tupla, due danno un dizionario. Ricorda quell'accoppiamento e il resto segue.
Parametri che possono essere passati solo per nome
Un parametro che sta dopo *args non può essere passato per posizione, perché *args si prende tutti gli argomenti posizionali rimanenti per sé. Quindi può essere passato solo per nome. Questo si chiama parametro solo per parola chiave:
def total(*prices, discount=0.0):
return sum(prices) * (1 - discount)
print(total(100, 50))
print(total(100, 50, discount=0.1))
print(total(100, 50, 0.1))150.0
135.0
150.1Guarda la terza riga. 0.1 non è diventato lo sconto — è finito in prices come un prezzo in più, e la risposta è sbagliata senza errore. Per passare discount devi scrivere discount=0.1.
Puoi rendere un parametro solo per parola chiave anche quando non vuoi *args: metti una * nuda nella lista dei parametri. Tutto ciò che viene dopo è solo per parola chiave:
def send(message, *, retry=False):
return f"{message} (retry={retry})"
print(send("hi", retry=True))
print(send("hi", True))hi (retry=True)
TypeError: send() takes 1 positional argument but 2 were given(Prima della seconda riga Python stampa anche alcune righe di traceback; qui è mostrata solo l'ultima.)
Questo è molto utile per le impostazioni True/False: chi chiama deve scrivere retry=True, così chi legge la chiamata vede a cosa serve quel True.
L'ordine è fisso
Scritti insieme, la regola completa è:
- Prima i parametri semplici. Tra questi, quelli senza valore predefinito vengono prima di quelli con valore predefinito.
- Poi
*args(o una*nuda). - Poi i parametri solo per parola chiave — con o senza valori predefiniti, in qualsiasi ordine.
- Per ultimo,
**kwargs.
def f(a, b=1, *args, c, d=2, **kwargs):
print(a, b, args, c, d, kwargs)
f(10, c=3)
f(10, 20, 30, 40, c=3, e=5)10 1 () 3 2 {}
10 20 (30, 40) 3 2 {'e': 5}c non ha valore predefinito, eppure viene dopo b=1, che ne ha uno, ed è legale — perché tutto ciò che viene dopo *args arriva per nome, quindi non c'è dubbio su dove vada ogni valore. La regola "predefiniti dopo" vale solo per la parte prima di *args.
Sembra pignola all'inizio, e la ragione è semplice: Python deve sapere quale argomento va dove, e poiché *args prende "tutto il resto", nessun parametro posizionale può seguirlo.
Scompattare in una chiamata
Le stelle si possono usare anche nella chiamata, dove fanno il lavoro opposto:
def order(item, quantity):
return f"{quantity} x {item}"
values = ["pen", 3]
fields = {"item": "bag", "quantity": 2}
print(order(*values))
print(order(**fields))3 x pen
2 x bag*values scompatta la lista — lo stesso che scrivere order(values[0], values[1]). **fields scompatta il dizionario, e le sue chiavi devono corrispondere ai nomi dei parametri.
Una stella in una definizione significa raccogliere; una stella in una chiamata significa scompattare — lo stesso simbolo, due direzioni.
Un esempio completo
invoice.py:
# Arguments, in the order Python expects them
def invoice(customer, *items, currency="unit", discount=0.0, **extra):
total = sum(price for _, price in items)
after = total * (1 - discount)
lines = [f"Invoice for {customer}"]
for name, price in items:
lines.append(f" {name:<10} {price:>8.2f} {currency}")
lines.append(f" {'subtotal':<10} {total:>8.2f} {currency}")
if discount:
lines.append(f" {'discount':<10} {discount * 100:>7.0f}%")
lines.append(f" {'total':<10} {after:>8.2f} {currency}")
for key, value in extra.items():
lines.append(f" note {key}: {value}")
return "\n".join(lines)
print(invoice("rafi", ("pen", 15.0), ("bag", 850.0)))
print()
print(invoice("dia", ("ink", 120.0), discount=0.1, reference="A-77"))Invoice for rafi
pen 15.00 unit
bag 850.00 unit
subtotal 865.00 unit
total 865.00 unit
Invoice for dia
ink 120.00 unit
subtotal 120.00 unit
discount 10%
total 108.00 unit
note reference: A-77Quattro cose che vale la pena notare.
customer è obbligatorio e tutto il resto è opzionale. Una fattura senza cliente non ha senso, quindi è un parametro semplice. Potrebbero non esserci articoli, quindi *items.
discount=0.0 non è solo un predefinito, semplifica una condizione. La riga if discount: usa la regola sulla verità del capitolo otto — 0.0 è falso, quindi senza sconto quella riga non viene mai stampata. E poiché discount viene dopo *items, è solo per parola chiave — va passato come discount=0.1.
`extra assorbe ciò che la funzione non conosce.** reference="A-77" non corrisponde a nessun parametro, quindi finisce in extra` e viene stampato come nota. È così che una funzione accetta campi a cui nessuno ha ancora pensato.
for _, price in items è lo scompattamento del capitolo quattordici, con l'underscore che dice che il nome non serve qui. items è una tupla di tuple, e ogni tupla interna viene scompattata.
Quando si rompe
SyntaxError: parameter without a default follows parameter with a default Un parametro con valore predefinito è stato messo prima di uno richiesto. Prima i richiesti, i predefiniti dopo.
TypeError: order() takes 2 positional arguments but 3 were given Troppi argomenti. Il messaggio dice sia quanti ne prende sia quanti ne sono arrivati.
TypeError: order() got an unexpected keyword argument 'qty' Passato per nome, ma nessun parametro ha quel nome. Controlla l'ortografia — oppure nota che una funzione con **kwargs non si sarebbe lamentata, l'avrebbe assorbito in silenzio.
TypeError: order() got multiple values for argument 'item' Lo stesso parametro è stato dato due volte, una per posizione e una per nome — order("pen", item="bag").
La funzione gira ma la risposta è al contrario Gli argomenti erano invertiti, e nessun errore è apparso perché il codice dentro la funzione funziona con entrambi i valori. Passa per nome — è l'errore silenzioso con cui si apriva questo capitolo.
*Scompattare con ` ma i conti non tornano** In order(*values), values deve contenere esattamente tante cose quanti parametri ha la funzione. Controlla con print(len(values))`.
Passo 4 di 6 — Prevedi
Verifica cosa hai capito
quantity ha un predefinito. Cosa stampano le due righe?
def order(item, quantity=1):
return f"{quantity} x {item}"
print(order("pen"))
print(order("pen", 5))- A1 x pen 5 x pen
- B1 x pen 1 x pen
- CUn `TypeError`
- Dpen x 1 pen x 5
Che cosa raccoglie *things?
def show(*things):
print(type(things))
print(len(things))
show(1, "two", 3.0)- A<class 'tuple'> 3
- B<class 'tuple'> 1
- C<class 'list'> 3
- D<class 'dict'> 3
Il parametro con predefinito è messo per primo. Cosa succede?
def order(quantity=1, item):
return item- AFunziona normalmente
- BFunziona, ma chiamare `order("pen")` fallisce
- CUn `SyntaxError` — un parametro con predefinito non può precedere uno richiesto
- DUn `TypeError`
Per rispondere serve un account
Accedi per verificare le tue risposte
Le domande sono qui sopra, e ragionarci a mente è la parte che conta. Accedi per vedere le risposte, le spiegazioni e i suggerimenti a tre livelli.
Tocca a te
Scrivi un file chiamato message.py contenente una funzione:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)Dovrebbe restituire un messaggio leggibile invece di stamparne uno, con il destinatario, l'oggetto, ogni riga sulla sua riga, e ogni coppia da details come nota sotto. Se urgent è True, aggiungi qualcosa prima dell'oggetto.
Poi chiamala in quattro modi:
- Con solo un destinatario e nessuna riga
- Con tre righe e tutto il resto ai suoi predefiniti
- Con
urgent=Truee unsubject, entrambi per nome - Costruendo una lista e un dizionario e scompattandoli con
*e**
Poi rompila tre volte apposta, e guarda cosa succede ogni volta:
- Sposta il parametro
subject="Notice"prima direcipient. Prima ancora che il file giri ottieniSyntaxError: parameter without a default follows parameter with a default. - Scrivi
notify("rafi", subjekt="hi"), con errore di ortografia. Non c'è errore:**detailsaccetta in silenzio anche il nome sbagliato, quindisubjektdiventa una nota e l'oggetto resta"Notice". Stampa il messaggio restituito e vedi da te. - Scrivi
notify("rafi", recipient="dia"). OttieniTypeError: notify() got multiple values for argument 'recipient'.
Python ne coglie due insieme e ne manca uno — e quello è il più pericoloso. È il prezzo della comodità che dà **kwargs: nessuno controlla più l'ortografia. Riconosci i messaggi una volta e li coglierai tra pochi secondi, e saprai anche dove si nasconde l'errore silenzioso.
Passo 6 di 6
Sfida — il quiz del capitolo
Dieci domande dalla più facile alla più difficile. Le ultime sono difficili di proposito.
Accedi per fare il quiz