`*args` and `**kwargs` — Flexible Function Arguments
As Python codebases grow, functions often need to handle varying inputs.
That’s where *args and **kwargs come in — they allow functions to accept an arbitrary number of arguments without constantly changing the function signature.
When used with intention, they make APIs more flexible and future-proof.
Understanding *args
*args collects extra positional arguments into a tuple.
def log_values(*args):
for value in args:
print(value)
log_values(1, 2, 3)
This is useful when:
- the number of inputs isn’t fixed
- order matters
- you want a simple, lightweight interface
Understanding **kwargs
**kwargs collects extra keyword arguments into a dictionary.
def log_config(**kwargs):
for key, value in kwargs.items():
print(key, value)
log_config(debug=True, retries=3)
This pattern is common in:
- configuration objects
- wrappers and decorators
- extensible APIs
Using *args and **kwargs together
def handler(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
handler(1, 2, debug=True)
This is often seen in libraries that need maximum flexibility.
Forwarding arguments
One of the most practical uses is argument forwarding:
def wrapper(*args, **kwargs):
return target_function(*args, **kwargs)
This allows you to wrap or extend behavior without changing the original interface.
A common mistake to avoid
Avoid using *args and **kwargs when the function expects a clear, fixed contract.
# Hard to understand API
def process(*args, **kwargs):
...
If the inputs are known and stable, explicit parameters are more readable and safer.
Conclusion
In my experience, *args and **kwargs shine when flexibility is truly needed.
Used sparingly, they help build clean and extensible APIs.
Overused, they can hide intent and make code harder to reason about.