Function Arguments: Defaults, *args and **kwargs
Positional, keyword, default, variable length and keyword only arguments - five styles, one fixed order, and one famous trap involving mutable defaults.
- Positional and keyword arguments
- Default values
- Defaults are evaluated once
- The mutable default argument trap
- *args - any number of positional arguments
- **kwargs - any number of keyword arguments
- The full parameter order
- Keyword only parameters
- Positional only parameters
- Forwarding arguments
- Common mistakes
- Best practices
- Practice
- Conclusion
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
Positional and keyword arguments
def describe(name, role, city):
return f"{name} is a {role} in {city}"
print(describe("Meera", "engineer", "Pune")) # positional
print(describe(name="Meera", role="engineer", city="Pune")) # keyword
print(describe("Meera", city="Pune", role="engineer")) # mixedPositional arguments are matched by order. Keyword arguments are matched by name, so their order is free. Once you use a keyword argument, everything after it must also be a keyword argument:
# print(describe(name="Meera", "engineer", "Pune"))
# SyntaxError: positional argument follows keyword argumentDefault values
def greet(name, greeting="Hello", punctuation="!"):
return f"{greeting}, {name}{punctuation}"
print(greet("Meera")) # Hello, Meera!
print(greet("Meera", "Welcome")) # Welcome, Meera!
print(greet("Meera", punctuation=".")) # Hello, Meera.Parameters with defaults must come after those without:
# def bad(greeting="Hello", name):
# SyntaxError: parameter without a default follows parameter with a defaultDefaults are evaluated once
import time
def stamp(label, when=time.time()): # evaluated when def RUNS
return f"{label}: {when}"
print(stamp("first"))
time.sleep(1)
print(stamp("second")) # the same timestamp - it was captured onceThe mutable default argument trap
def add_item(item, basket=[]): # WRONG
basket.append(item)
return basket
print(add_item("pen")) # ['pen']
print(add_item("book")) # ['pen', 'book'] <- the same list came back
print(add_item("bag")) # ['pen', 'book', 'bag']The empty list is created once, when the def statement executes, and every call that omits the argument shares it. The fix never varies:
def add_item(item, basket=None): # correct
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item("pen")) # ['pen']
print(add_item("book")) # ['book']Rule: a default value may be a number, a string, a tuple, None or another immutable object. Never a list, dictionary, set or a call that produces a fresh object.*args - any number of positional arguments
def total(*numbers):
print(type(numbers)) # <class 'tuple'>
return sum(numbers)
print(total()) # 0
print(total(1, 2, 3)) # 6
print(total(*[4, 5, 6])) # 15 - a list spread into arguments
def log(level, *messages):
for message in messages:
print(f"[{level}] {message}")
log("INFO", "started", "connected", "ready")*args collects the extra positional arguments into a tuple. The name args is a convention; the star is the syntax.
**kwargs - any number of keyword arguments
def configure(**options):
print(type(options)) # <class 'dict'>
for key, value in options.items():
print(f"{key} = {value}")
configure(theme="dark", size=14)
settings = {"theme": "light", "wrap": True}
configure(**settings) # a dictionary spread into keyword argumentsThe full parameter order
def f(pos_only, /, standard, *args, kw_only, **kwargs):
| | | | |
| | | | +-- extra keyword arguments
| | | +------------ must be given by name
| | +--------------------- extra positional arguments
| +------------------------------ positional or keyword
+------------------------------------------- positional onlydef report(title, *sections, author="unknown", **metadata):
print(f"{title} by {author}")
for section in sections:
print(" -", section)
for key, value in metadata.items():
print(f" {key}: {value}")
report("Q3 Review", "Summary", "Findings", author="Meera", version=2, draft=True)Keyword only parameters
def connect(host, port, *, timeout=30, retries=3):
return f"{host}:{port} timeout={timeout} retries={retries}"
print(connect("localhost", 8080, timeout=5))
# print(connect("localhost", 8080, 5)) # TypeError: too many positional argumentsEverything after a bare * must be passed by name. Use this for options, so that a call site never reads as connect("localhost", 8080, 5, 2) with no clue what the numbers mean.
Positional only parameters
def distance(x, y, /):
return abs(x - y)
print(distance(3, 10)) # 7
# print(distance(x=3, y=10)) # TypeError: positional onlyEverything before a / must be passed by position. Since Python 3.8. It is used mainly by library authors who want to keep parameter names free to rename later.
Forwarding arguments
def log_call(func, *args, **kwargs):
print(f"calling {func.__name__} with {args} {kwargs}")
result = func(*args, **kwargs)
print(f" -> {result}")
return result
def area(width, height=1):
return width * height
log_call(area, 4, height=5)Collecting with *args, **kwargs and passing them straight on is the standard wrapper pattern, and it is exactly how decorators work.
Common mistakes
- Using a mutable default value.
- Putting a positional argument after a keyword argument at the call site.
- Defining a parameter with a default before one without.
- Confusing
*argsat the definition (collect) with*listat the call (spread). - Passing a list to
*argswithout the star, so the whole list becomes one argument. - Using
**kwargseverywhere, hiding the real interface from readers and editors.
Best practices
- Default mutable arguments to
Noneand build the real value inside. - Use keyword only parameters for anything optional, especially booleans and numbers.
- Name arguments at the call site when the value alone is not self explanatory.
- Prefer explicit parameters to
**kwargs; use**kwargsonly for genuine pass through. - Keep the parameter count small. Four or more usually means a dataclass is waiting to be created.
Practice
- Write a function accepting any number of numbers and returning their mean, handling the empty case.
- Demonstrate the mutable default trap across three calls, then fix it.
- Write a function with two required parameters and three keyword only options.
- Write a wrapper that logs every call to any function and forwards all arguments.
- Explain the difference between
f(*items)at a call anddef f(*items)at a definition.
Conclusion
Five argument styles, one fixed order, and one rule that prevents the classic bug: never use a mutable default. Use keyword only parameters for options and your call sites will explain themselves.