Skip to content

Python *args und **kwargs erklärt: Der vollständige Leitfaden

Updated on

Jeder Python-Entwickler stößt irgendwann einmal auf diese Wand. Sie öffnen den Quellcode einer Bibliothek, den Pull-Request eines Kollegen oder ein Open-Source-Projekt und sehen Funktionssignaturen, die gespickt sind mit *args und **kwargs. Was bedeuten die Sternchen? Warum gibt es einmal ein und zweimal zwei davon? Wann sollte man welches verwenden? Wenn man sie falsch anwendet, produziert das verwirrende TypeError-Meldungen wie "takes 2 positional arguments but 5 were given" oder "got an unexpected keyword argument", und plötzlich wird ein unkomplizierter Funktionsaufruf zu einer Debug-Sitzung.

Das Problem verschärft sich, wenn Sie selbst flexible Funktionen schreiben müssen. Alle Parameter fest zu codieren macht Ihre API starr. Zu viele benannte Parameter zu akzeptieren macht die Funktionssignatur unleserlich. Sie brauchen eine Möglichkeit, Funktionen zu schreiben, die eine variable Anzahl von Argumenten akzeptieren, ohne dabei Klarheit zu opfern.

Python löst dies mit zwei speziellen Syntax-Funktionen: *args für variable Positionsargumente und **kwargs für variable Schlüsselwortargumente. Dieser Leitfaden erklärt beide von Grund auf, behandelt die Entpackungsoperatoren, führt durch reale Muster und hilft Ihnen, die häufigsten Fehler zu vermeiden.

📚

Was sind *args und **kwargs?

In Python sind *args und **kwargs Konventionen für das Akzeptieren einer variablen Anzahl von Argumenten in Funktionsdefinitionen.

  • *args sammelt zusätzliche Positionsargumente in einem Tupel.
  • **kwargs sammelt zusätzliche Schlüsselwortargumente in einem Dictionary.

Die Namen args und kwargs sind Konventionen, keine Vorgaben. Die Magie kommt von den Präfixen * und **, nicht von den Namen selbst. Sie könnten *values und **options schreiben, und sie würden identisch funktionieren.

Hier ist die einfachste Demonstration:

def show_args(*args, **kwargs):
    print(f"args   = {args}")
    print(f"kwargs = {kwargs}")
 
show_args(1, 2, 3, name="Alice", age=30)
# args   = (1, 2, 3)
# kwargs = {'name': 'Alice', 'age': 30}

Positionsargumente (Werte ohne Namen übergeben) landen in args als Tupel. Schlüsselwortargumente (Werte mit key=value-Syntax übergeben) landen in kwargs als Dictionary. Das ist das gesamte Kernkonzept.

*args verstehen: Variable Positionsargumente

Das einzelne Sternchen * vor einem Parameternamen teilt Python mit, alle verbleibenden Positionsargumente in ein Tupel zu packen. Das ermöglicht Ihrer Funktion, eine beliebige Anzahl von Positionswerten zu akzeptieren.

Grundlegende Syntax und Verwendung

def add_all(*args):
    """Sum any number of values."""
    total = 0
    for num in args:
        total += num
    return total
 
print(add_all(1, 2))            # 3
print(add_all(1, 2, 3, 4, 5))   # 15
print(add_all(10))               # 10
print(add_all())                 # 0

Innerhalb der Funktion ist args ein reguläres Python-Tupel. Sie können darüber iterieren, per Index darauf zugreifen, seine Länge prüfen und es an andere Funktionen übergeben.

def describe_args(*args):
    print(f"Type: {type(args)}")
    print(f"Length: {len(args)}")
    print(f"First element: {args[0] if args else 'N/A'}")
    print(f"Contents: {args}")
 
describe_args("hello", 42, True)
# Type: <class 'tuple'>
# Length: 3
# First element: hello
# Contents: ('hello', 42, True)

Kombination regulärer Parameter mit *args

Sie können Standardparameter mit *args mischen. Alle regulären Positionsparameter werden zuerst gefüllt, und alle verbleibenden Positionsargumente gehen in *args:

def log_message(level, *args):
    """Log a message with a severity level."""
    message = " ".join(str(a) for a in args)
    print(f"[{level.upper()}] {message}")
 
log_message("info", "Server started on port", 8080)
# [INFO] Server started on port 8080
 
log_message("error", "Connection failed:", "timeout after", 30, "seconds")
# [ERROR] Connection failed: timeout after 30 seconds

Praxisbeispiel: Eine flexible Durchschnittsfunktion

def average(*values):
    """Calculate the average of any number of values."""
    if not values:
        raise ValueError("average() requires at least one argument")
    return sum(values) / len(values)
 
print(average(85, 90, 78))         # 84.33333333333333
print(average(100))                 # 100.0
print(average(72, 88, 95, 67, 91)) # 82.6

Praxisbeispiel: Hilfsfunktion zur Zeichenkettenformatierung

def build_path(*segments):
    """Join path segments with forward slashes, stripping extras."""
    cleaned = [seg.strip("/") for seg in segments if seg]
    return "/" + "/".join(cleaned)
 
print(build_path("api", "v2", "users", "123"))
# /api/v2/users/123
 
print(build_path("/data/", "/reports/", "2026/", "sales.csv"))
# /data/reports/2026/sales.csv

**kwargs verstehen: Variable Schlüsselwortargumente

Das doppelte Sternchen ** vor einem Parameternamen teilt Python mit, alle verbleibenden Schlüsselwortargumente in ein Dictionary zu packen. Das ermöglicht Ihrer Funktion, eine beliebige Anzahl von benannten Werten zu akzeptieren.

Grundlegende Syntax und Verwendung

def print_info(**kwargs):
    """Print key-value pairs in a formatted way."""
    for key, value in kwargs.items():
        print(f"  {key}: {value}")
 
print_info(name="Alice", age=30, city="Seattle")
#   name: Alice
#   age: 30
#   city: Seattle

Innerhalb der Funktion ist kwargs ein standardmäßiges Python-Dictionary. Sie können .get(), .keys(), .values(), .items() und jede andere Dictionary-Methode verwenden.

def describe_kwargs(**kwargs):
    print(f"Type: {type(kwargs)}")
    print(f"Keys: {list(kwargs.keys())}")
    print(f"Values: {list(kwargs.values())}")
 
describe_kwargs(x=10, y=20, z=30)
# Type: <class 'dict'>
# Keys: ['x', 'y', 'z']
# Values: [10, 20, 30]

Praxisbeispiel: Konfigurations-Builder

def create_connection(host, port, **kwargs):
    """Create a database connection with optional settings."""
    config = {
        "host": host,
        "port": port,
        "timeout": kwargs.get("timeout", 30),
        "retries": kwargs.get("retries", 3),
        "ssl": kwargs.get("ssl", True),
        "pool_size": kwargs.get("pool_size", 5),
    }
 
    # Add any extra settings the caller provided
    for key, value in kwargs.items():
        if key not in config:
            config[key] = value
 
    return config
 
# Basic usage
basic = create_connection("localhost", 5432)
print(basic)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'retries': 3, 'ssl': True, 'pool_size': 5}
 
# With custom options
custom = create_connection(
    "db.example.com", 5432,
    timeout=60,
    ssl=False,
    application_name="my_app"
)
print(custom)
# {'host': 'db.example.com', 'port': 5432, 'timeout': 60, 'retries': 3, 'ssl': False, 'pool_size': 5, 'application_name': 'my_app'}

Praxisbeispiel: HTML-Tag-Builder

def html_tag(tag, content="", **attributes):
    """Generate an HTML tag with optional attributes."""
    attr_str = ""
    for key, value in attributes.items():
        # Convert Python naming to HTML (class_ -> class)
        html_key = key.rstrip("_")
        attr_str += f' {html_key}="{value}"'
 
    if content:
        return f"<{tag}{attr_str}>{content}</{tag}>"
    return f"<{tag}{attr_str} />"
 
print(html_tag("a", "Click here", href="https://example.com", class_="btn"))
# <a href="https://example.com" class="btn">Click here</a>
 
print(html_tag("img", src="photo.jpg", alt="A photo", width="200"))
# <img src="photo.jpg" alt="A photo" width="200" />
 
print(html_tag("p", "Hello world", id="intro", style="color: blue"))
# <p id="intro" style="color: blue">Hello world</p>

*args und **kwargs zusammen verwenden

Sie können beide in derselben Funktionsdefinition verwenden, um jede Kombination aus Positions- und Schlüsselwortargumenten zu akzeptieren. Die Reihenfolge der Parameter folgt strengen Regeln.

Regeln zur Parameterreihenfolge

Python erzwingt diese exakte Reihenfolge in Funktionssignaturen:

  1. Reguläre Positionsparameter
  2. *args (variable Positionsargumente)
  3. Nur-Schlüsselwort-Parameter (nach *args)
  4. **kwargs (variable Schlüsselwortargumente)
def example(a, b, *args, option=True, **kwargs):
    print(f"a = {a}")
    print(f"b = {b}")
    print(f"args = {args}")
    print(f"option = {option}")
    print(f"kwargs = {kwargs}")
 
example(1, 2, 3, 4, 5, option=False, color="red", size=10)
# a = 1
# b = 2
# args = (3, 4, 5)
# option = False
# kwargs = {'color': 'red', 'size': 10}

Hier ist eine Zusammenfassung der Parametertypen und ihrer Reihenfolge:

PositionTypSyntaxBeispielBeschreibung
1.Positionalparama, bErforderlich, wird per Position gefüllt
2.Defaultparam=valuec=10Optional, wird per Position oder Name gefüllt
3.Variable Positional*args*argsSammelt zusätzliche Positionsargumente
4.Nur-Schlüsselwortparam (nach *)option=TrueMuss per Name übergeben werden
5.Variable Keyword**kwargs**kwargsSammelt zusätzliche Schlüsselwortargumente

Häufiges Muster: Durchreichfunktion

Eines der nützlichsten Muster mit *args und **kwargs ist das Erstellen von Funktionen, die alle Argumente an eine andere Funktion weitergeben:

def timed_call(func, *args, **kwargs):
    """Call a function and measure its execution time."""
    import time
    start = time.perf_counter()
    result = func(*args, **kwargs)
    elapsed = time.perf_counter() - start
    print(f"{func.__name__} took {elapsed:.4f}s")
    return result
 
def expensive_sum(a, b, c):
    import time
    time.sleep(0.1)
    return a + b + c
 
result = timed_call(expensive_sum, 10, 20, c=30)
# expensive_sum took 0.1003s
print(result)
# 60

Entpacken mit * und **

Die Operatoren * und ** funktionieren in beide Richtungen: Sie packen Argumente in Funktionsdefinitionen und entpacken Argumente in Funktionsaufrufen und anderen Kontexten.

Entpacken von Listen und Tupeln mit *

Verwenden Sie *, um ein Iterable in einzelne Positionsargumente zu entpacken:

def add(a, b, c):
    return a + b + c
 
numbers = [10, 20, 30]
 
# Without unpacking - this causes TypeError
# add(numbers)  # TypeError: add() missing 2 required positional arguments
 
# With unpacking - spreads list into separate arguments
result = add(*numbers)
print(result)  # 60
 
# Works with tuples, sets, and any iterable
coords = (5, 10, 15)
print(add(*coords))  # 30

Sie können * auch für das Entpacken in Zuweisungen und Listenkonstruktionen verwenden (Python 3.5+):

# Extended unpacking in assignments
first, *middle, last = [1, 2, 3, 4, 5]
print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5
 
# Unpacking in list/tuple construction
list_a = [1, 2, 3]
list_b = [4, 5, 6]
combined = [*list_a, *list_b]
print(combined)  # [1, 2, 3, 4, 5, 6]
 
# Unpacking with additional elements
extended = [0, *list_a, 99, *list_b, 100]
print(extended)  # [0, 1, 2, 3, 99, 4, 5, 6, 100]

Entpacken von Dictionaries mit **

Verwenden Sie **, um ein Dictionary in Schlüsselwortargumente zu entpacken:

def create_user(name, email, role="viewer"):
    return {"name": name, "email": email, "role": role}
 
user_data = {"name": "Alice", "email": "alice@example.com", "role": "admin"}
 
# Unpack dict into keyword arguments
user = create_user(**user_data)
print(user)
# {'name': 'Alice', 'email': 'alice@example.com', 'role': 'admin'}

Zusammenführen von Dictionaries mit **

Eine der praktischsten Anwendungen von ** ist das Zusammenführen von Dictionaries:

defaults = {"color": "blue", "size": 12, "font": "Arial"}
user_prefs = {"color": "red", "size": 16}
 
# Merge: user_prefs override defaults
merged = {**defaults, **user_prefs}
print(merged)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
 
# Python 3.9+ also supports the | operator
merged_new = defaults | user_prefs
print(merged_new)
# {'color': 'red', 'size': 16, 'font': 'Arial'}
 
# Adding extra keys during merge
final = {**defaults, **user_prefs, "theme": "dark"}
print(final)
# {'color': 'red', 'size': 16, 'font': 'Arial', 'theme': 'dark'}

Kombiniertes Entpacken mit * und **

Sie können beide Operatoren zusammen beim Aufruf einer Funktion verwenden:

def report(title, *items, separator="---", **metadata):
    print(f"== {title} ==")
    for item in items:
        print(f"  - {item}")
    print(separator)
    for key, value in metadata.items():
        print(f"  {key}: {value}")
 
positional = ["Task A", "Task B", "Task C"]
options = {"author": "Alice", "date": "2026-02-14"}
 
report("Sprint Review", *positional, separator="===", **options)
# == Sprint Review ==
#   - Task A
#   - Task B
#   - Task C
# ===
#   author: Alice
#   date: 2026-02-14

Praktische Muster

Muster 1: Dekorator-Funktionen

Die häufigste Verwendung von *args und **kwargs in Produktiv-Python ist das Schreiben von Dekoratoren. Ein Dekorator umschließt eine Funktion mit einer anderen. Da Sie die Signatur der umschlossenen Funktion nicht im Voraus kennen, müssen Sie *args und **kwargs verwenden, um alle Argumente weiterzuleiten:

import functools
import time
 
def retry(max_attempts=3, delay=1.0):
    """Retry a function up to max_attempts times on failure."""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    print(f"Attempt {attempt}/{max_attempts} failed: {e}")
                    if attempt < max_attempts:
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator
 
@retry(max_attempts=3, delay=0.5)
def fetch_data(url, timeout=10):
    """Simulate fetching data that might fail."""
    import random
    if random.random() < 0.6:
        raise ConnectionError(f"Failed to connect to {url}")
    return f"Data from {url}"
 
# The decorator forwards url and timeout through *args/**kwargs
result = fetch_data("https://api.example.com", timeout=5)
print(result)

Muster 2: Weiterleitung in Subklassen-init

Beim Vererben müssen Sie oft Konstruktorargumente an die Elternklasse weiterleiten. *args und **kwargs machen dies sauber:

class Animal:
    def __init__(self, name, species, sound="..."):
        self.name = name
        self.species = species
        self.sound = sound
 
    def speak(self):
        return f"{self.name} says {self.sound}"
 
class Dog(Animal):
    def __init__(self, *args, breed="Unknown", **kwargs):
        super().__init__(*args, **kwargs)
        self.breed = breed
 
    def info(self):
        return f"{self.name} ({self.breed}) - {self.species}"
 
# All Animal parameters pass through seamlessly
dog = Dog("Rex", "Canine", sound="Woof!", breed="German Shepherd")
print(dog.speak())  # Rex says Woof!
print(dog.info())   # Rex (German Shepherd) - Canine

Dieses Muster ist essenziell bei der Arbeit mit komplexen Klassenhierarchien, besonders in Frameworks wie Django, Flask oder SQLAlchemy, wo Sie Basisklassen erweitern.

Muster 3: Wrapper- und Proxy-Funktionen

Wenn Sie Funktionsaufrufe abfangen oder modifizieren müssen, ohne die Originalfunktion zu ändern:

def log_call(func):
    """Log every call to a function with its arguments."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args_repr = [repr(a) for a in args]
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
        signature = ", ".join(args_repr + kwargs_repr)
        print(f"Calling {func.__name__}({signature})")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned {result!r}")
        return result
    return wrapper
 
@log_call
def calculate_discount(price, discount_pct, tax_rate=0.08):
    discounted = price * (1 - discount_pct / 100)
    return round(discounted * (1 + tax_rate), 2)
 
calculate_discount(100, 20, tax_rate=0.1)
# Calling calculate_discount(100, 20, tax_rate=0.1)
# calculate_discount returned 88.0

Muster 4: API-Client-Builder

Das Erstellen flexibler API-Wrapper ist ein klassischer Anwendungsfall:

import json
 
class APIClient:
    def __init__(self, base_url, **default_headers):
        self.base_url = base_url.rstrip("/")
        self.default_headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            **default_headers,
        }
 
    def request(self, method, endpoint, *args, **kwargs):
        """Build a request with merged headers and parameters."""
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        headers = {**self.default_headers, **kwargs.pop("headers", {})}
 
        request_info = {
            "method": method,
            "url": url,
            "headers": headers,
            **kwargs,
        }
        print(json.dumps(request_info, indent=2, default=str))
        return request_info
 
    def get(self, endpoint, **kwargs):
        return self.request("GET", endpoint, **kwargs)
 
    def post(self, endpoint, **kwargs):
        return self.request("POST", endpoint, **kwargs)
 
# Usage
client = APIClient(
    "https://api.example.com",
    Authorization="Bearer token123"
)
 
client.get("/users", params={"page": 1, "limit": 50})
client.post("/users", data={"name": "Alice"}, headers={"X-Request-ID": "abc123"})

Muster 5: Data Science -- Dynamische Plotting-Parameter

Beim Erstellen von Datenanalysefunktionen ermöglicht **kwargs, Konfigurationen an zugrunde liegende Bibliotheken durchzureichen:

import pandas as pd
 
def analyze_column(df, column, **plot_kwargs):
    """Analyze a dataframe column and generate summary statistics."""
    stats = {
        "count": df[column].count(),
        "mean": df[column].mean(),
        "std": df[column].std(),
        "min": df[column].min(),
        "max": df[column].max(),
    }
 
    print(f"\nAnalysis of '{column}':")
    for stat, value in stats.items():
        print(f"  {stat}: {value:.2f}")
 
    # Forward any extra kwargs to the plot function
    plot_defaults = {"kind": "hist", "bins": 20, "title": f"Distribution of {column}"}
    plot_config = {**plot_defaults, **plot_kwargs}
 
    # df[column].plot(**plot_config)  # Uncomment with matplotlib installed
    print(f"  Plot config: {plot_config}")
    return stats
 
# Create sample data
df = pd.DataFrame({
    "revenue": [100, 250, 180, 320, 275, 410, 195, 360],
    "quantity": [5, 12, 8, 15, 13, 20, 9, 17],
})
 
# Default analysis
analyze_column(df, "revenue")
 
# Custom plot settings passed through **kwargs
analyze_column(df, "revenue", kind="box", color="steelblue", figsize=(10, 6))

Wenn Sie in Jupyter-Notebooks arbeiten und interaktiv mit Funktionssignaturen experimentieren möchten, bietet RunCell (opens in a new tab) eine KI-gestützte Notebook-Umgebung, in der Sie *args- und **kwargs-Muster testen, Echtzeit-Vorschläge für die Parameterhandhabung erhalten und Probleme beim Argument-Passing debuggen können, ohne Ihren Workflow zu verlassen.

Häufige Fehler und wie man sie behebt

Hier sind die häufigsten Fehler, auf die Python-Entwickler bei *args und **kwargs stoßen, zusammen mit deren Lösungen:

FehlerFehlermeldungUrsacheLösung
Falsche ParameterreihenfolgeSyntaxError: invalid syntax**kwargs vor *args platziertImmer die Reihenfolge verwenden: regulär, *args, nur-Schlüsselwort, **kwargs
Liste übergeben statt zu entpackenTypeError: func() missing required arguments[1,2,3] statt *[1,2,3] übergeben* zum Entpacken verwenden: func(*my_list)
Doppeltes SchlüsselwortargumentTypeError: got multiple values for argument 'x'Derselbe Schlüssel sowohl als Positional als auch in **kwargsSicherstellen, dass keine Überschneidung zwischen Positional-Args und Dictionary-Keys besteht
kwargs direkt modifizierenUnerwartete SeiteneffekteDas kwargs-Dict mutierenkwargs.copy() oder {**kwargs, ...} verwenden
*args in super().init vergessenTypeError: __init__() missing argumentsArgs nicht an Elternklasse weitergeleitetsuper().__init__(*args, **kwargs) verwenden
Mutable Default mit kwargs verwendenGeteilter Zustand zwischen Aufrufendef func(data={})None als Default verwenden: def func(data=None)

Beispiel: Doppeltes Schlüsselwortargument

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"
 
data = {"name": "Alice", "greeting": "Hi"}
 
# WRONG: name is passed both as positional AND in **data
# greet("Alice", **data)
# TypeError: greet() got multiple values for argument 'name'
 
# CORRECT: pass only through unpacking
print(greet(**data))
# Hi, Alice!
 
# OR: remove the duplicate key
print(greet("Alice", **{"greeting": "Hi"}))
# Hi, Alice!

Beispiel: Sicheres Modifizieren von kwargs

def process(name, **kwargs):
    # WRONG: modifying kwargs directly affects the caller's dict
    # kwargs["processed"] = True
 
    # CORRECT: create a new dict
    config = {**kwargs, "processed": True}
    return {"name": name, **config}
 
settings = {"timeout": 30, "retries": 3}
result = process("task1", **settings)
print(result)
# {'name': 'task1', 'timeout': 30, 'retries': 3, 'processed': True}
 
# Original dict is unchanged
print(settings)
# {'timeout': 30, 'retries': 3}

*args/**kwargs vs. andere Ansätze

Wann sollten Sie *args und **kwargs statt Alternativen verwenden? Hier ist ein Vergleich:

AnsatzSyntaxAm besten geeignet fürNachteile
*argsdef f(*args)Unbekannte Anzahl gleichartiger PositionswerteKeine Type Hints pro Argument, kein benannter Zugriff
**kwargsdef f(**kwargs)Flexible Optionen, Durchreichung an andere FunktionenKeine Auto-Vervollständigung in IDEs, keine statische Typprüfung
Explizite Parameterdef f(a, b, c)Bekannte, feste Menge von ArgumentenStarr; das Hinzufügen von Parametern bricht bestehende Aufrufe
Default-Parameterdef f(a, b=10)Optionale Parameter mit sinnvollen DefaultsErfordert trotzdem, alle Optionen im Voraus zu kennen
Listen-/Tupel-Parameterdef f(items: list)Geordnete Sammlung von WertenAufrufer muss die Liste explizit konstruieren
Dict-Parameterdef f(options: dict)Strukturierte KonfigurationAufrufer muss das Dict explizit konstruieren
TypedDict/dataclassdef f(config: Config)Strukturierte, typsichere KonfigurationMehr Boilerplate, erfordert Klassendefinition

Allgemeine Richtlinien:

  • Verwenden Sie explizite Parameter, wenn die Menge der Argumente bekannt und stabil ist.
  • Verwenden Sie *args, wenn Ihre Funktion naturgemäß auf einer variablen Anzahl gleichartiger Werte operiert (wie print(), max(), min()).
  • Verwenden Sie **kwargs, wenn Sie Optionen an eine andere Funktion durchreichen, flexible Konfiguration akzeptieren oder erweiterbare APIs bauen müssen.
  • Verwenden Sie beide, wenn Sie Dekoratoren, Proxy-Funktionen oder Klassenhierarchien schreiben, die vollständiges Argument-Forwarding benötigen.
  • Verwenden Sie TypedDict oder Dataclass, wenn Sie IDE-Auto-Vervollständigung und statische Typprüfung für strukturierte Konfiguration wünschen.

Type Hints mit *args und **kwargs hinzufügen

Python 3.11+ erlaubt Ihnen, Type Hints mit *args und **kwargs unter Verwendung von Unpack und TypedDict hinzuzufügen:

# Basic type hints (all args same type)
def add_numbers(*args: float) -> float:
    return sum(args)
 
def set_options(**kwargs: str) -> dict[str, str]:
    return kwargs
 
# Python 3.11+: precise kwargs typing with TypedDict
from typing import TypedDict, Unpack
 
class ConnectionOptions(TypedDict, total=False):
    timeout: int
    retries: int
    ssl: bool
 
def connect(host: str, port: int, **kwargs: Unpack[ConnectionOptions]) -> dict:
    return {"host": host, "port": port, **kwargs}
 
# IDE now knows the valid keyword arguments
result = connect("localhost", 5432, timeout=30, ssl=True)
print(result)
# {'host': 'localhost', 'port': 5432, 'timeout': 30, 'ssl': True}

Häufig gestellte Fragen

Wofür stehen *args und **kwargs?

Die Namen args und kwargs sind Kurzformen für "arguments" und "keyword arguments". Sie sind rein konventionelle Namen -- die eigentliche Magie kommt von den Präfix-Operatoren * und **. Das einzelne Sternchen * teilt Python mit, zusätzliche Positionsargumente in ein Tupel zu packen, während das doppelte Sternchen ** zusätzliche Schlüsselwortargumente in ein Dictionary packt. Sie werden diese Namen in praktisch jeder Python-Codebasis, jedem Tutorial und jeder Bibliothek sehen, weil sie die universell anerkannte Konvention sind, aber die Sprache erfordert diese spezifischen Namen nicht.

Kann ich andere Namen als args und kwargs verwenden?

Ja. Die Namen args und kwargs sind Konventionen, keine Syntax-Vorgaben. Das Entpack-Verhalten kommt vollständig von den Präfixen * und **. Sie können *values, *items, *numbers, **options, **config, **params oder jeden anderen gültigen Python-Identifier schreiben. Es wird jedoch dringend empfohlen, bei *args und **kwargs zu bleiben, weil jeder Python-Entwickler sie sofort erkennt. Verwenden Sie benutzerdefinierte Namen nur, wenn ein beschreibenderer Name die Lesbarkeit wirklich verbessert, wie *paths in einer Datei-Handling-Funktion oder **headers in einem HTTP-Client.

Was ist die korrekte Reihenfolge der Parameter in einer Python-Funktion?

Python erzwingt eine strenge Reihenfolge: Zuerst kommen reguläre Positionsparameter, dann *args, dann nur-Schlüsselwort-Parameter (mit Default-Werten) und schließlich **kwargs. Die vollständige Reihenfolge ist: def func(pos1, pos2, default1=val, *args, kw_only1, kw_only2=val, **kwargs). Das Verletzen dieser Reihenfolge produziert einen SyntaxError. Eine nützliche Eselsbrücke ist "positional, star-args, keyword-only, double-star-kwargs" -- die Anzahl der Sterne nimmt von links nach rechts zu.

Wann sollte ich *args vs. einen Listen-Parameter verwenden?

Verwenden Sie *args, wenn jedes Argument ein separater, unabhängiger Wert ist und der Aufrufer sie natürlich übergeben sollte, ohne einen Container zu konstruieren: print("a", "b", "c") ist natürlicher als print(["a", "b", "c"]). Verwenden Sie einen Listen-Parameter, wenn die Werte logisch eine Sammlung bilden, die der Aufrufer bereits in einer Variable hat, oder wenn Sie zwischen der Sammlung und anderen Parametern unterscheiden müssen. Built-in-Funktionen wie max(), min() und print() verwenden *args, weil die Aufrufkonvention natürlich wirkt, während Funktionen wie sorted(iterable) ein einzelnes Iterable annehmen, weil die Eingabe inhärent eine Sequenz ist.

Sind *args und **kwargs langsam?

Der Overhead von *args und **kwargs ist minimal. Python erstellt bei jedem Aufruf ein Tupel für *args und ein Dictionary für **kwargs, was kleine Speicherallokationen beinhaltet. In Benchmarks ist der Unterschied im Vergleich zu expliziten Parametern typischerweise einige hundert Nanosekunden pro Aufruf -- irrelevant für praktisch jede reale Anwendung. Sie würden Millionen von Aufrufen in einer engen Schleife benötigen, bevor dieser Overhead messbar wird. Konzentrieren Sie sich auf algorithmische Effizienz und I/O-Optimierung, statt *args/**kwargs zu vermeiden. Die Flexibilität und Code-Wartbarkeit, die sie bieten, überwiegen bei Weitem jeden Mikro-Performance-Kosten.

Fazit

Pythons *args und **kwargs sind zwei der praktischsten Features in der Sprache. Sie lösen ein fundamentales Problem: Wie schreibt man Funktionen, die flexibel genug sind, um eine variable Anzahl von Argumenten zu akzeptieren, ohne dabei Lesbarkeit zu opfern.

Die wichtigsten Erkenntnisse:

  • *args sammelt zusätzliche Positionsargumente in einem Tupel. Verwenden Sie es, wenn Ihre Funktion eine beliebige Anzahl von Werten akzeptieren soll.
  • **kwargs sammelt zusätzliche Schlüsselwortargumente in einem Dictionary. Verwenden Sie es für flexible Optionen, Konfigurations-Durchreichung und erweiterbare APIs.
  • Parameterreihenfolge ist immer: regulär, *args, nur-Schlüsselwort, **kwargs.
  • Entpacken mit * und ** funktioniert in Funktionsaufrufen, Listenkonstruktionen und dem Zusammenführen von Dictionaries.
  • Dekoratoren sind der wichtigste Anwendungsfall in der Praxis -- sie verlassen sich auf *args und **kwargs, um jede Funktion unabhängig von ihrer Signatur zu umschließen.

Beginnen Sie mit expliziten Parametern für Funktionen mit bekannten, stabilen Signaturen. Greifen Sie zu *args und **kwargs, wenn Sie Flexibilität brauchen: Dekoratoren, Subklassen-Forwarding, Wrapper-Funktionen und API-Builder. Sobald Sie die Mechanik des Packens und Entpackens verinnerlicht haben, werden Sie saubereren, wiederverwendbareren Python-Code schreiben.

📚