*args and **kwargs let a function accept any number of arguments. *args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dictionary. Together, they make functions flexible without requiring a fixed parameter list.
| Read | Build | Watch | Test | Review | Visualize |
|---|---|---|---|---|---|
| You are here | Projects | Videos | Quiz | Flashcards | Diagrams |
You will see *args and **kwargs in almost every Python library. Understanding them is essential for writing decorators, creating wrapper functions, and understanding how frameworks like Flask and pytest work under the hood. They also appear in function signatures on docs.python.org — you need to read them confidently.
The * before a parameter name collects all extra positional arguments into a tuple:
def add_all(*args):
print(type(args)) # <class 'tuple'>
print(args) # (1, 2, 3, 4, 5)
return sum(args)
add_all(1, 2, 3, 4, 5) # 15
add_all(10, 20) # 30
add_all() # 0The name args is a convention — you can use any name. The * is what matters:
def greet(*names):
for name in names:
print(f"Hello, {name}!")
greet("Alice", "Bob", "Charlie")The ** before a parameter name collects all extra keyword arguments into a dictionary:
def print_info(**kwargs):
print(type(kwargs)) # <class 'dict'>
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=30, city="Portland")
# name: Alice
# age: 30
# city: PortlandAgain, kwargs is just a convention. The ** is what matters.
def make_profile(name, *hobbies, **details):
print(f"Name: {name}")
print(f"Hobbies: {hobbies}")
print(f"Details: {details}")
make_profile("Alice", "reading", "hiking", age=30, city="Portland")
# Name: Alice
# Hobbies: ('reading', 'hiking')
# Details: {'age': 30, 'city': 'Portland'}The order must be: regular parameters, then *args, then **kwargs.
The * and ** operators also work in the opposite direction — unpacking a sequence or dictionary into function arguments:
def add(a, b, c):
return a + b + c
# Unpack a list into positional arguments:
numbers = [1, 2, 3]
add(*numbers) # Same as add(1, 2, 3) → 6
# Unpack a dict into keyword arguments:
params = {"a": 10, "b": 20, "c": 30}
add(**params) # Same as add(a=10, b=20, c=30) → 60This is extremely useful for forwarding arguments:
def wrapper(*args, **kwargs):
print("Before the call")
result = original_function(*args, **kwargs)
print("After the call")
return resultPython 3.8+ lets you mark parameters as positional-only using /:
def greet(name, /, greeting="Hello"):
return f"{greeting}, {name}!"
greet("Alice") # OK: "Hello, Alice!"
greet("Alice", greeting="Hi") # OK: "Hi, Alice!"
greet(name="Alice") # TypeError! name is positional-onlyEverything before / must be passed by position, not by name. You see this in built-in functions like len() — you cannot write len(obj=[1,2,3]).
A bare * in the parameter list forces everything after it to be keyword-only:
def connect(host, port, *, timeout=30, retries=3):
print(f"Connecting to {host}:{port} (timeout={timeout})")
connect("localhost", 8080) # OK
connect("localhost", 8080, timeout=10) # OK
connect("localhost", 8080, 10) # TypeError! timeout is keyword-onlyThis prevents mistakes where someone passes arguments in the wrong order.
def example(pos_only, /, normal, *, kw_only, **kwargs):
pass
# pos_only — must be positional (before /)
# normal — can be positional or keyword
# kw_only — must be keyword (after *)
# **kwargs — catches extra keyword argumentsThe complete order is:
- Positional-only parameters (before
/) - Regular parameters
*args- Keyword-only parameters (after
*or*args) **kwargs
Decorator that passes through all arguments:
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapperConfig builder:
def create_config(name, **overrides):
defaults = {
"debug": False,
"port": 8080,
"host": "localhost",
}
return {**defaults, **overrides, "name": name}
config = create_config("myapp", debug=True, port=3000)
# {"debug": True, "port": 3000, "host": "localhost", "name": "myapp"}Merging dictionaries with **:
defaults = {"color": "blue", "size": "medium"}
user_prefs = {"color": "red", "font": "Arial"}
merged = {**defaults, **user_prefs}
# {"color": "red", "size": "medium", "font": "Arial"}
# Later values override earlier ones*Mutable default arguments (not specific to args but related):
# WRONG — the list is shared between all calls:
def add_item(item, items=[]):
items.append(item)
return items
add_item("a") # ["a"]
add_item("b") # ["a", "b"] — surprise!
# RIGHT — use None as default:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return itemsForgetting to unpack:
def add(a, b):
return a + b
args = (1, 2)
add(args) # TypeError — passes the tuple as a single argument
add(*args) # 3 — unpacks into two argumentsWrong order of parameters:
# WRONG:
def bad(*args, name, **kwargs): # name after *args is keyword-only
pass
bad("a", "b", "Alice") # TypeError! name must be keyword
bad("a", "b", name="Alice") # OK- Level 2 / 01 JSON Explorer
- Module 02 CLI Tools — Click/Typer use these patterns
- Module 04 FastAPI Web — endpoint parameter handling
Quick check: Take the quiz (coming soon)
Review: Flashcard decks Practice reps: Coding challenges
- More on Defining Functions (Python tutorial)
- PEP 570 — Positional-Only Parameters
- PEP 3102 — Keyword-Only Arguments
| ← Prev | Home | Next → |
|---|