Python-Kurs / Argumente mit *args und **kwargs sammeln und entpacken

In einer Funktionsdefinition sammelt *args zusätzliche Positionsargumente in einem Tupel und **kwargs zusätzliche Schlüsselwortargumente in einem Wörterbuch. Beim Aufruf wirken * und ** in Gegenrichtung: Sie entpacken vorhandene Elemente zu einzelnen Argumenten.

Sammeln in der Definition und Entpacken beim Aufruf

Die Namen args und kwargs sind Konventionen; die Sternchen bestimmen die Funktion. Im Beispiel heißt der Sammelparameter amounts, damit sein Inhalt erkennbar ist. Verwenden Sie variable Argumentlisten, wenn tatsächlich beliebig viele gleichartige Werte sinnvoll sind. Bei feststehenden Eingaben sind normale benannte Parameter oft leichter zu verstehen und zu prüfen.

Stelle Form Wirkung
Definition def f(*args) Zusätzliche Positionswerte als Tupel sammeln
Definition def f(**kwargs) Zusätzliche Schlüsselwortwerte als Wörterbuch sammeln
Aufruf f(*values) Elemente eines iterierbaren Objekts als Positionsargumente übergeben
Aufruf f(**options) Einträge einer Zuordnung mit Zeichenkettenschlüsseln als Schlüsselwortargumente übergeben

Ein Parameter hinter *amounts ist ein Schlüsselwortparameter. Hier muss die Gebühr daher als fee=500 angegeben werden, wenn sie vom Standardwert 0 abweichen soll. Ein weiterer Positionswert würde in amounts landen. Die Regeln ergänzen die Übung zu Schlüsselwortargumenten und Standardwerten. Die Python-Einführung zu variablen Argumentlisten beschreibt die Sammlung und die nachfolgenden Schlüsselwortparameter.

Beliebig viele Beträge und benannte Felder verarbeiten

Speichern Sie den vollständigen Code als varargs.py in UTF-8 im Übungsordner. Starten Sie python varargs.py mit dem Projektinterpreter. Unter macOS oder Ubuntu kann außerhalb einer aktivierten Umgebung python3 passen. Das Beispiel benötigt keine Zusatzpakete und keine externen Daten.

def total(*amounts, fee=0):
    return sum(amounts) + fee

def describe(**fields):
    return ", ".join(f"{key}={value}" for key, value in sorted(fields.items()))

values = [3500, 8000, 2500]
print(total(*values, fee=500))
options = {"category": "food", "currency": "KRW"}
print(describe(**options))

Erwartete Ausgabe:

14500
category=food, currency=KRW

*values übergibt 3500, 8000 und 2500 einzeln. Die Funktion sammelt diese Werte in amounts, summiert sie zu 14000 und addiert die Gebühr 500. Dagegen wäre total(values) ein einziger Positionswert, nämlich eine Liste. Die gezeigte Implementierung erwartet an dieser Stelle einzelne Zahlen und würde beim Summieren der Liste als Element einen TypeError auslösen.

describe(**options) entpackt die beiden Zuordnungseinträge als Schlüsselwortargumente. Die Funktion sammelt sie unter fields, sortiert die Paare nach ihrem Schlüssel und verbindet die formatierten Texte mit Komma und Leerzeichen. Dadurch ist die sichtbare Reihenfolge hier alphabetisch. category, food und KRW sind unveränderte Beispieldaten, keine überprüften externen Optionen.

Doppelte Schlüssel und falsche Objekte erkennen

Beim Entpacken mit ** müssen die Schlüssel Zeichenketten sein. Ob die Namen zulässig sind, hängt von der Zieldefinition ab: describe nimmt beliebige zusätzliche Namen an, eine fest definierte Funktion kann unbekannte Namen zurückweisen. Auch ein Wörterbuch mit ganzzahligen Schlüsseln kann existieren, eignet sich aber nicht unverändert als Schlüsselwortargumente eines Aufrufs.

Wenn Sie describe(category="food", **{"category": "bus"}) aufrufen, ist category doppelt vorhanden und es entsteht ein TypeError. Das wird nicht dadurch erlaubt, dass describe beliebige Schlüsselwörter annimmt. Eine vorher erzeugte zusammengeführte Zuordnung hat dagegen bereits nur einen Wert pro Schlüssel. Die Regeln für das Zusammenführen von Wörterbüchern und die Regeln für einen Aufruf dürfen Sie daher nicht gleichsetzen. Die Sprachreferenz zu Aufrufen beschreibt die Bindung und Fehlersituationen.

Auch * prüft nicht, ob seine Quelle fachlich zu den Parametern passt. *"123" liefert drei Zeichenketten, keine Zahl 123. total(*"123") scheitert deshalb bei der Summenberechnung. Eine Zahl wie 123 ist ihrerseits nicht iterierbar und kann nicht mit * als Quelle entpackt werden.

In einer Weiterleitungsfunktion sammelt def wrapper(*args, **kwargs): beide Argumentarten. Die eingerückte Rückgabe return total(*args, **kwargs) entpackt sie für die Zielfunktion. Ein Aufruf wrapper(3500, 8000, fee=500) liefert damit 12000. Würden Sie stattdessen das Wörterbuch ohne ** übergeben, wäre es nur ein weiteres Positionsargument und hätte eine andere Bedeutung.

Grenzfälle testen und akzeptierte Optionen begrenzen

  1. Fügen Sie nur eine 0 zu values hinzu. Das Ergebnis mit Gebühr bleibt 14500.
  2. Entfernen Sie nur fee=500 aus dem ursprünglichen Aufruf. Die Standardgebühr 0 ergibt 14000.
  3. Rufen Sie total() auf: Ohne Beträge und ohne Gebühr ergibt sich 0. Mit total(fee=500) ist das Ergebnis 500. Entscheiden Sie ausdrücklich, ob ein leerer Betragsbestand für Ihre Anwendung erlaubt sein soll.
  4. Prüfen Sie describe(): Es liefert eine leere Zeichenkette. Prüfen Sie getrennt den doppelten Schlüssel und einen nicht als Zeichenkette vorliegenden Schlüssel; beide Aufrufe müssen scheitern.
  5. Stellen Sie die ursprünglichen Werte und Aufrufe wieder her und vergleichen Sie die beiden Ausgabezeilen.

Die Möglichkeit, beliebige Schlüsselwörter zu sammeln, ersetzt keine Prüfung. describe(catgeory="food") würde den Tippfehler annehmen und anzeigen. Soll eine Schnittstelle nur bestimmte Felder verstehen, prüfen Sie erlaubte Namen und Pflichtfelder, bevor Sie Werte weiterreichen. Übergeben Sie ungeprüfte Optionen nicht unmittelbar an eine externe API oder Datenbankabfrage. Eine einzelne ausdrücklich benannte Sammlung kann die verständlichere Schnittstelle sein, wenn sie ein zusammengehöriges Datenobjekt repräsentiert.