Skip to content

Args, Kwargs & Parameters

Python distinguishes between parameters (names in the function definition) and arguments (values passed at call time). Python gives you fine-grained control over how callers can supply those values.

By default, every parameter can be supplied positionally or by keyword name.

def connect(host: str, port: int, timeout: int = 30) -> str:
return f"{host}:{port} (timeout={timeout}s)"
# Positional
connect("localhost", 5432)
# Keyword — order does not matter
connect(port=5432, host="localhost")
# Mixed — positional first, then keyword
connect("localhost", port=5432, timeout=60)

timeout has a default value of 30, making it optional. Positional arguments must always come before keyword arguments at the call site.

Prefix a parameter with * to collect any number of positional arguments into a tuple.

def total(*amounts: float) -> float:
return sum(amounts)
print(total(10.0, 20.5, 5.0)) # 35.5
print(total()) # 0.0

Prefix a parameter with ** to collect any number of keyword arguments into a dict.

def log_event(event: str, **metadata) -> None:
parts = [f"event={event}"]
for key, value in metadata.items():
parts.append(f"{key}={value}")
print(", ".join(parts))
log_event("login", user_id=42, ip="127.0.0.1")
# event=login, user_id=42, ip=127.0.0.1

Any parameter listed after a bare * (or after *args) can only be supplied by keyword — never positionally. This makes APIs self-documenting and prevents argument order mistakes.

def send_email(to: str, subject: str, *, cc: str = "", bcc: str = "") -> None:
print(f"To: {to} | Subject: {subject} | CC: {cc} | BCC: {bcc}")
send_email("[email protected]", "Hello", cc="[email protected]")
# Calling send_email("[email protected]", "Hi", "[email protected]") would raise TypeError

The order of parameter kinds in a definition must always be:

  1. Positional-or-keyword (x, y)
  2. *args (or bare *)
  3. Keyword-only (key=value after *)
  4. **kwargs
def describe(
name: str,
*adjectives: str,
separator: str = ", ",
**extras
) -> str:
adj_str = separator.join(adjectives) if adjectives else "no adjectives"
extra_str = ", ".join(f"{k}={v}" for k, v in extras.items())
result = f"{name}: {adj_str}"
if extra_str:
result += f" ({extra_str})"
return result
print(describe("Python"))
print(describe("Python", "fast", "readable"))
print(describe("Python", "fast", "readable", separator=" | "))
print(describe("Python", "fast", separator=" + ", version="3.11", typed=True))
What type does *args collect arguments into?
A parameter defined after a bare `*` in a function signature is:
Why is `def f(items=[])` dangerous?
What is the correct order of parameter kinds in a Python function signature?