Functie-argumenten — namen, standaarden, en sterren
Argumenten bij naam doorgeven, standaardwaarden en de regel over hun volgorde, *args en **kwargs, en een lijst of dictionary uitpakken in een aanroep.
Hoe wil je leren?
Het probleem dat we oplossen
De functies in het vorige hoofdstuk konden maar op één manier worden aangeroepen: evenveel argumenten als er parameters zijn, in precies die volgorde.
In de praktijk is dat niet genoeg. Kijk naar een functie voor een bestelling:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3De tweede regel gaf geen fout — het leverde gewoon een zinloos antwoord. Python controleert niet de typen van argumenten: hier wisselden een string en een getal van plek, en omdat een f-string beide kan printen, stopte niets het. Python heeft geen manier om te weten dat de volgorde omgekeerd was.
En als bijna elke bestelling voor één item is? Je zou telkens 1 typen. Als een functie vier optionele instellingen heeft? Je zou alle vier telkens doorgeven, inclusief degenen die je niet wijzigt.
Dit hoofdstuk behandelt de rest van de manieren om argumenten door te geven: bij naam, met standaardwaarden, en in onbekend aantal.
Aan het einde van dit hoofdstuk kun je
- Argumenten bij naam doorgeven, en zeggen wanneer je dat zou doen
- Standaardwaarden geven, en de regel over hun volgorde volgen
- Sommige parameters keyword-only maken, zodat ze alleen bij naam kunnen worden doorgegeven
- Elk aantal argumenten opvangen met
*args - Argumenten van elke naam opvangen met
**kwargs - Een lijst of een dictionary uitpakken in een aanroep
Voorkennis: Functies — één keuze, één naam.
Doorgeven bij naam
In plaats van de volgorde te onthouden, schrijf je de naam:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penMet namen maakt de volgorde niet meer uit — quantity werd eerst geschreven en landde toch op de juiste plek.
Wanneer zou je dat doen? Wanneer het argument zich niet zelf uitlegt. order("pen", 3) leest prima. Maar send(True, False) vertelt je niets, terwijl send(retry=True, verbose=False) dat wel doet. Als regel: geef ze namen als je True/False of een los getal doorgeeft.
Standaardwaarden
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 zegt "neem één aan als niemand anders zegt". Het veelvoorkomende geval wordt kort, en het zeldzame geval blijft uitdrukbaar.
Één regel moet worden nageleefd: parameters met standaardwaarden komen altijd laatst.
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultDe reden is duidelijk zodra je erover nadenkt: gegeven order("pen"), hoe zou Python weten voor welke parameter "pen" bedoeld is? Met de verplichte parameters eerst, is er geen onduidelijkheid.
Elk aantal argumenten — *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0De ster in *prices zegt "verzamel de overige positionele argumenten hier". Je hoeft van tevoren niet te beslissen hoeveel er komen, en helemaal geen is prima.
Wat is het verzamelde ding?
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')Een tuple — de tuple uit hoofdstuk veertien. De redenering is netjes: hoeveel argumenten er zijn, wordt bepaald op het moment van de aanroep en zou daarna niet meer mogen veranderen, dus past een onveranderlijke container.
De naampricesis niet verplicht. De conventionele naam is*args, wat je in de meeste code ziet. Maar een betekenisvolle naam is beter waar die bestaat —*pricesvertelt een lezer wat er binnenkomt,*argsniet.
Argumenten van elke naam — **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: 3Twee sterren verzamelen de argumenten die bij naam zijn doorgegeven, en het resultaat is een dictionary — paren van naam en waarde, wat precies is wat het zou moeten zijn.
Eén ster geeft een tuple, twee geven een dictionary. Onthoud die koppeling en de rest volgt vanzelf.
Parameters die alleen bij naam mogen worden doorgegeven
Een parameter die na *args staat kan niet per positie worden doorgegeven, omdat *args elke overgebleven positionele argument voor zich neemt. Dus hij kan alleen bij naam worden doorgegeven. Dit wordt een keyword-only-parameter genoemd:
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.1Kijk naar de derde regel. 0.1 werd niet de korting — het ging in prices als een extra prijs, en het antwoord is fout zonder foutmelding. Om discount door te geven moet je discount=0.1 schrijven.
Je kunt een parameter keyword-only maken zelfs als je geen *args wilt: zet een blote * in de parameterlijst. Alles erna is keyword-only:
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(Vóór de tweede regel print Python ook enkele traceback-regels; hier wordt alleen de laatste regel getoond.)
Dit is erg handig voor True/False-instellingen: de aanroeper moet retry=True schrijven, dus iedereen die de aanroep leest ziet waar de True voor is.
De volgorde is vast
Samen geschreven is de volledige regel:
- Gewone parameters eerst. Onder hen komen degenen zonder standaardwaarde vóór degenen met een standaardwaarde.
- Dan
*args(of een blote*). - Dan keyword-only parameters — met of zonder standaardwaarden, in willekeurige volgorde.
- Laatst
**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 heeft geen standaardwaarde, komt toch na b=1 die er wel een heeft, en dat is legaal — omdat alles na *args bij naam binnenkomt, dus er geen twijfel is over waar elke waarde heen gaat. De regel "verplichte eerst, standaarden erna" geldt alleen voor het deel vóór *args.
Het ziet er op het eerste gezicht friemelig uit, en de reden is simpel: Python moet weten welk argument waar heen gaat, en aangezien *args "alles wat overblijft" pakt, kan er geen positionele parameter erna volgen.
Uitpakken in een aanroep
De sterren kunnen ook bij de aanroep worden gebruikt, waar ze het tegenovergestelde doen:
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 pakt de lijst uit — hetzelfde als order(values[0], values[1]) schrijven. **fields pakt de dictionary uit, en de sleutels moeten overeenkomen met de parameternamen.
Een ster in een definitie betekent verzamelen; een ster in een aanroep betekent uitpakken — hetzelfde symbool, twee richtingen.
Een compleet voorbeeld
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-77Vier dingen die opvallen.
customer is verplicht en de rest is optioneel. Een factuur zonder klant is zinloos, dus dat is een gewone parameter. Er mogen geen items zijn, dus *items.
discount=0.0 is niet alleen een standaardwaarde, het vereenvoudigt een voorwaarde. De if discount:-regel gebruikt hoofdstuk acht zijn truthiness-regel — 0.0 is false, dus zonder korting wordt die regel nooit geprint. En omdat discount na *items komt, is het keyword-only — het moet worden doorgegeven als discount=0.1.
`extra vangt op wat de functie niet kent.** reference="A-77" matcht geen parameter, dus het landt in extra` en wordt geprint als notitie. Zo accepteert een functie velden waar nog niemand aan heeft gedacht.
for _, price in items is hoofdstuk veertien zijn uitpakken, met de underscore die zegt dat de naam hier niet nodig is. items is een tuple van tuples, en elke innerlijke tuple wordt uitgepakt.
Wanneer het breekt
SyntaxError: parameter without a default follows parameter with a default Een parameter met een standaardwaarde werd voor een verplichte geplaatst. Verplichte eerst, standaarden erna.
TypeError: order() takes 2 positional arguments but 3 were given Te veel argumenten. De melding zegt zowel hoeveel het neemt als hoeveel er kwamen.
TypeError: order() got an unexpected keyword argument 'qty' Bij naam doorgegeven, maar geen parameter heeft die naam. Controleer de spelling — of let op dat een functie met **kwargs niet zou klagen, het zou het stilletjes opvangen.
TypeError: order() got multiple values for argument 'item' Dezelfde parameter werd tweemaal gegeven, een keer per positie en een keer bij naam — order("pen", item="bag").
De functie draait maar het antwoord is omgekeerd De argumenten waren omgewisseld, en er verscheen geen fout omdat de code in de functie met beide waarden werkt. Geef argumenten bij naam door — dit is de stille fout waarmee dit hoofdstuk opende.
*Uitpakken met ` maar de aantallen komen niet overeen** Bij order(*values) moet values precies zoveel dingen bevatten als de functie parameters heeft. Controleer met print(len(values))`.
Stap 4 van 6 — Voorspellen
Test je begrip
quantity heeft een standaardwaarde. Wat printen de twee regels?
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
- CEen `TypeError`
- Dpen x 1 pen x 5
Wat voor ding verzamelt *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
De parameter met een standaardwaarde wordt eerst geplaatst. Wat gebeurt er?
def order(quantity=1, item):
return item- AHet werkt normaal
- BHet werkt, maar `order("pen")` faalt
- CEen `SyntaxError` — een parameter met een standaardwaarde mag niet voor een verplichte parameter komen
- DEen `TypeError`
Voor antwoorden heb je een account nodig
Log in om je antwoorden te controleren
De vragen staan hierboven, en ze in je hoofd uitwerken is waar het om draait. Log in om de antwoorden, de uitleg en de hints in drie niveaus te zien.
Jouw beurt
Schrijf een bestand genaamd message.py met één functie:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)De functie zou een leesbaar bericht moeten `return`en in plaats van te printen, met de ontvanger, het onderwerp, elke regel op een eigen rij, en elk paar uit details als notitie eronder. Als urgent True is, voeg dan iets toe vóór het onderwerp.
Roep het daarna vier manieren aan:
- Alleen met een ontvanger en helemaal geen regels
- Met drie regels en alles anders op de standaardwaarde
- Met
urgent=Trueen eensubject, beide bij naam - Door een lijst en een dictionary te bouwen en ze uit te pakken met
*en**
En breekt het dan drie keer exprès, en zie wat er elke keer gebeurt:
- Verplaats de
subject="Notice"-parameter vóórrecipient. Nog voor het bestand draait krijg jeSyntaxError: parameter without a default follows parameter with a default. - Schrijf
notify("rafi", subjekt="hi"), verkeerd gespeld. Er is geen fout:**detailsaccepteert de verkeerde naam ook stilletjes, dussubjektwordt een notitie en het onderwerp blijft"Notice". Print het geretourneerde bericht en zie het zelf. - Schrijf
notify("rafi", recipient="dia"). Je krijgtTypeError: notify() got multiple values for argument 'recipient'.
Python vangt er twee van deze fouten direct op en slaat er één over — en die is de gevaarlijkste. Dit is de prijs van het gemak dat **kwargs geeft: niemand controleert meer de spelling. Herken de meldingen een keer en je vangt ze later in seconden, en je weet ook waar de stille fout zich verbergt.
Stap 6 van 6
Uitdaging — de hoofdstukquiz
Tien vragen van makkelijk naar moeilijk. De laatste zijn met opzet lastig.
Log in om de quiz te maken