Funktionsargumente — Namen, Standardwerte und Sterne
Argumente per Namen übergeben, Standardwerte und die Regel zu ihrer Reihenfolge, *args und **kwargs, und eine Liste oder ein Dictionary in einen Aufruf entpacken.
Wie möchtest du lernen?
Das Problem, das wir lösen
Die Funktionen im letzten Kapitel konnten nur auf eine Weise aufgerufen werden: so viele Argumente wie Parameter, in genau dieser Reihenfolge.
In der Praxis reicht das nicht. Denk an eine Funktion für eine Bestellung:
def order(item, quantity):
return f"{quantity} x {item}"
print(order("pen", 3))
print(order(3, "pen"))3 x pen
pen x 3Die zweite Zeile warf keinen Fehler — sie lieferte einfach eine sinnlose Antwort. Python prüft die Typen der Argumente nicht: hier vertauschten ein String und eine Zahl die Plätze, und weil ein f-String beides drucken kann, hielt nichts auf. Python hat keine Möglichkeit zu wissen, dass die Reihenfolge vertauscht war.
Und wenn fast jede Bestellung nur für ein Stück ist? Du würdest jedes Mal 1 tippen. Wenn eine Funktion vier optionale Einstellungen hat? Du würdest alle vier jedes Mal übergeben, auch die, die du nicht änderst.
Dieses Kapitel behandelt die übrigen Arten, Argumente zu übergeben: mit Namen, mit Standardwerten und in unbekannter Anzahl.
Am Ende dieses Kapitels kannst du
- Argumente mit Namen übergeben und sagen, wann du das tun solltest
- Standardwerte vergeben und die Regel zu ihrer Reihenfolge befolgen
- Einige Parameter als Keyword-only machen, damit sie nur mit Namen übergeben werden können
- Beliebig viele Argumente mit
*argsentgegennehmen - Argumente beliebiger Namen mit
**kwargsentgegennehmen - Eine Liste oder ein Dictionary in einen Aufruf entpacken
Voraussetzungen: Funktionen — eine Entscheidung, ein Name.
Mit Namen übergeben
Statt dir die Reihenfolge zu merken, schreibst du den Namen:
def order(item, quantity):
return f"{quantity} x {item}"
print(order(quantity=3, item="pen"))3 x penMit Namen spielt die Reihenfolge keine Rolle mehr — quantity wurde zuerst geschrieben und landete trotzdem am richtigen Platz.
Wann solltest du das? Wenn das Argument sich nicht selbst erklärt. order("pen", 3) liest sich gut. Aber send(True, False) sagt dir nichts, während send(retry=True, verbose=False) es tut. Als Regel: nenne sie, wenn du True/False oder eine nackte Zahl übergibst.
Standardwerte
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 bedeutet „nimm eins an, wenn niemand etwas anderes sagt“. Der häufige Fall wird kurz, und der seltene ist weiterhin ausdrückbar.
Eine Regel muss befolgt werden: Parameter mit Standardwerten kommen immer zuletzt.
def order(quantity=1, item):
return itemSyntaxError: parameter without a default follows parameter with a defaultDer Grund wird klar, sobald du darüber nachdenkst: bei order("pen") — wie soll Python wissen, für welchen Parameter "pen" gemeint ist? Mit den erforderlichen zuerst gibt es keine Mehrdeutigkeit.
Beliebig viele Argumente — *args
def total(*prices):
return sum(prices)
print(total(15, 60))
print(total(15, 60, 850))
print(total())75
925
0Der Stern in *prices sagt „sammle die übrigen Positionsargumente hier ein“. Du musst nicht im Voraus entscheiden, wie viele kommen, und gar keine ist auch okay.
Was wird gesammelt?
def show(*things):
print(type(things))
print(things)
show(1, "two")<class 'tuple'>
(1, 'two')Ein Tupel — das Tupel aus Kapitel vierzehn. Die Begründung ist sauber: wie viele Argumente es sind, steht im Moment des Aufrufs fest und sollte sich danach nicht ändern, also passt ein unveränderlicher Container.
Der Namepricesist nicht vorgeschrieben. Der konventionelle Name ist*args, den du in den meisten Codes siehst. Aber ein sprechender Name ist besser, wo es einen gibt —*pricessagt einem Leser, was ankommt,*argsnicht.
Argumente beliebiger Namen — **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: 3Zwei Sterne sammeln die mit Namen übergebenen Argumente, und das Ergebnis ist ein Dictionary — Paare aus Name und Wert, genau das, was es sein sollte.
Ein Stern gibt ein Tupel, zwei geben ein Dictionary. Merke dir dieses Paar und der Rest folgt.
Parameter, die nur mit Namen übergeben werden können
Ein Parameter, der hinter *args steht, kann nicht per Position übergeben werden, weil *args sich jedes verbleibende Positionsargument schnappt. Also kann er nur mit Namen übergeben werden. Das nennt man einen Keyword-only-Parameter:
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.1Schau dir die dritte Zeile an. 0.1 wurde nicht zum Rabatt — es landete in prices als ein weiterer Preis, und die Antwort ist falsch ohne Fehler. Um discount zu übergeben, musst du discount=0.1 schreiben.
Du kannst einen Parameter keyword-only machen, auch wenn du *args nicht willst: setz ein nacktes * in die Parameterliste. Alles dahinter ist 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(Bevor die zweite Zeile lief, druckte Python auch einige Traceback-Zeilen; hier wird nur die letzte Zeile gezeigt.)
Das ist sehr nützlich für True/False-Einstellungen: der Aufrufer muss retry=True schreiben, also kann jeder, der den Aufruf liest, sehen, wofür das True steht.
Die Reihenfolge ist fest
Zusammen geschrieben lautet die vollständige Regel:
- Einfache Parameter zuerst. Unter ihnen kommen die ohne Standardwert vor denen mit Standardwert.
- Dann
*args(oder ein nacktes*). - Dann Keyword-only-Parameter — mit oder ohne Standardwerte, in beliebiger Reihenfolge.
- Zum Schluss
**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 hat keinen Standardwert, steht aber hinter b=1, der einen hat, und das ist erlaubt — weil alles hinter *args mit Namen ankommt, gibt es keinen Zweifel, wohin jeder Wert gehört. Die „Defaults nach hinten“-Regel gilt nur für den Teil vor *args.
Das wirkt zunächst pingelig, und der Grund ist simpel: Python muss wissen, welches Argument wohin gehört, und da *args „alles Übrige“ nimmt, kann ihm kein Positionsparameter folgen.
In einen Aufruf entpacken
Die Sterne können auch beim Aufruf verwendet werden, wo sie die gegenteilige Arbeit machen:
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 entpackt die Liste — dasselbe wie order(values[0], values[1]) zu schreiben. **fields entpackt das Dictionary, und seine Schlüssel müssen mit den Parameternamen übereinstimmen.
Ein Stern in einer Definition heißt sammeln; ein Stern beim Aufruf heißt entpacken — dasselbe Symbol, zwei Richtungen.
Ein vollständiges Beispiel
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 Dinge, die auffallen.
customer ist erforderlich und alles andere optional. Eine Rechnung ohne Kunde ist sinnlos, also ist das ein einfacher Parameter. Es kann gar keine Posten geben, also *items.
discount=0.0 ist nicht nur ein Standardwert, er vereinfacht eine Bedingung. Die if discount:-Zeile nutzt die Wahrheitsregel aus Kapitel acht — 0.0 ist falsch, also wird ohne Rabatt diese Zeile nie gedruckt. Und weil discount hinter *items steht, ist es keyword-only — es muss als discount=0.1 übergeben werden.
`extra schluckt, was die Funktion nicht kennt.** reference="A-77" passt zu keinem Parameter, also landet es in extra` und wird als Notiz gedruckt. So nimmt eine Funktion Felder entgegen, an die noch niemand gedacht hat.
for _, price in items ist das Entpacken aus Kapitel vierzehn, wobei der Unterstrich sagt, dass der Name hier nicht gebraucht wird. items ist ein Tupel von Tupeln, und jedes innere Tupel wird entpackt.
Wenn etwas schiefgeht
SyntaxError: parameter without a default follows parameter with a default Ein Parameter mit Standardwert wurde vor einen erforderlichen gesetzt. Erforderliche zuerst, Defaults danach.
TypeError: order() takes 2 positional arguments but 3 were given Zu viele Argumente. Die Meldung sagt sowohl, wie viele sie nimmt, als auch wie viele ankamen.
TypeError: order() got an unexpected keyword argument 'qty' Mit Namen übergeben, aber kein Parameter hat diesen Namen. Prüf die Schreibweise — oder beachte, dass eine Funktion mit **kwargs nicht gemeckert hätte, sondern es still geschluckt hätte.
TypeError: order() got multiple values for argument 'item' Derselbe Parameter wurde zweimal übergeben, einmal per Position und einmal per Name — order("pen", item="bag").
Die Funktion läuft, aber die Antwort ist verkehrt herum Die Argumente waren vertauscht, und kein Fehler erschien, weil der Code in der Funktion mit beiden Werten funktioniert. Übergebe mit Namen — das ist der stille Fehler, mit dem das Kapitel begann.
*Entpacken mit `, aber die Anzahlen stimmen nicht** Bei order(*values) muss values genau so viele Dinge halten, wie die Funktion Parameter hat. Prüf mit print(len(values))`.
Schritt 4 von 6 — Vorhersagen
Prüfe dein Verständnis
quantity hat einen Standardwert. Was drucken die beiden Zeilen?
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
- CEin `TypeError`
- Dpen x 1 pen x 5
Was für ein Ding sammelt *things ein?
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
Der Parameter mit Standardwert steht zuerst. Was passiert?
def order(quantity=1, item):
return item- AEs funktioniert normal
- BEs funktioniert, aber `order("pen")` schlägt fehl
- CEin `SyntaxError` — ein Parameter mit Standardwert darf keinem erforderlichen vorausgehen
- DEin `TypeError`
Zum Antworten brauchst du ein Konto
Melde dich an, um deine Antworten zu prüfen
Die Fragen stehen oben, und sie im Kopf durchzuarbeiten ist der wichtige Teil. Melde dich an, um die Antworten, die Erklärungen und die dreistufigen Hinweise zu sehen.
Jetzt bist du dran
Schreib eine Datei namens message.py mit einer Funktion:
def notify(recipient, *lines, subject="Notice", urgent=False, **details)Sie sollte eine lesbare Nachricht zurückgeben statt eine zu drucken, mit dem Empfänger, dem Betreff, jeder Zeile in ihrer eigenen Reihe und jedem Paar aus details als Notiz darunter. Wenn urgent True ist, füge etwas vor den Betreff ein.
Ruf sie dann auf vier Arten auf:
- Nur mit einem Empfänger und gar keinen Zeilen
- Mit drei Zeilen und allem anderen auf Standard
- Mit
urgent=Trueund einemsubject, beide mit Namen - Indem du eine Liste und ein Dictionary baust und sie mit
*und**entpackst
Dann brich sie absichtlich dreimal, und schau, was jedes Mal passiert:
- Verschiebe den Parameter
subject="Notice"vorrecipient. Noch bevor die Datei läuft, bekommst duSyntaxError: parameter without a default follows parameter with a default. - Schreib
notify("rafi", subjekt="hi"), falsch geschrieben. Es gibt keinen Fehler:**detailsakzeptiert den falschen Namen stillschweigend auch, also wirdsubjektzur Notiz und der Betreff bleibt"Notice". Druck die zurückgegebene Nachricht und sieh es selbst. - Schreib
notify("rafi", recipient="dia"). Du bekommstTypeError: notify() got multiple values for argument 'recipient'.
Python erwischt zwei dieser Fehler sofort und lässt einen durch — und der ist der gefährlichste. Das ist der Preis für die Bequemlichkeit, die **kwargs gibt: niemand prüft mehr die Schreibweise. Erkenne die Meldungen einmal, und du fängst sie später in Sekunden, und du weißt auch, wo der stille Fehler sich versteckt.
Schritt 6 von 6
Herausforderung — das Kapitel-Quiz
Zehn Fragen von leicht bis schwer. Die letzten sind absichtlich knifflig.
Melde dich an, um das Quiz zu machen