You've mastered unpacking, which splits an iterable into individual variables. Now, let's look at the opposite process: variable packing. Packing is about collecting multiple, independent values into a single variable, typically a tuple or a dictionary. This is a crucial concept, especially when building flexible functions.
Packing Positional Arguments with *args
The single asterisk (*
) is a packing operator when used in a function definition. It allows a function to accept a variable number of positional arguments (arguments passed without a keyword) and collects them all into a single tuple. The conventional name for this parameter is args
, short for "arguments."
Imagine you want a function that can calculate the sum of any number of values, without having to define a separate parameter for each one. This is a perfect job for *args
.
def my_sum(*args):
"""Calculates the sum of any number of values."""
return sum(args)
# You can call the function with any number of arguments
print(my_sum(1, 2, 3))
print(my_sum(10, 20, 30, 40, 50))
Output:
6
150
To see whatโs happening under the hood, letโs inspect the args
variable itself in a new example. Itโs a tuple containing all the positional arguments.
def inspect_args(*args):
print(f"Packed tuple: {args}")
print(f"Type: {type(args)}")
inspect_args(1, "hello", True)
Output:
Packed tuple: (1, 'hello', True)
Type: <class 'tuple'>
Packing Keyword Arguments with **kwargs
The double asterisk (**
) is used to pack a variable number of keyword arguments (arguments passed in the key=value
format) into a single dictionary. The conventional name is kwargs
, short for "keyword arguments."
This is incredibly useful for functions that take an arbitrary set of configuration options or user details. Here's a real-world-inspired example for an API client function.
def configure_api(**kwargs):
"""Configures an API client with optional settings."""
defaults = {'timeout': 5, 'retries': 3, 'api_key': 'default'}
defaults.update(kwargs)
print("--- API Configuration ---")
for key, value in defaults.items():
print(f"{key.replace('_', ' ').title()}: {value}")
print("-------------------------")
configure_api(api_key='your_secret_key', retries=5)
Output:
--- API Configuration ---
Timeout: 5
Retries: 5
Api Key: your_secret_key
-------------------------
Using Both *args
and **kwargs
You can even combine both *args
and **kwargs
in a single function definition. When you do, the order matters: positional arguments come first, followed by *args
, and finally **kwargs
. This is a strict rule in Python.
def flexible_function(required_arg, *args, **kwargs):
print(f"Required Argument: {required_arg}")
print(f"Positional Arguments (*args): {args}")
print(f"Keyword Arguments (**kwargs): {kwargs}")
flexible_function("hello", 1, 2, 3, a=10, b=20)
Output:
Required Argument: hello
Positional Arguments (*args): (1, 2, 3)
Keyword Arguments (**kwargs): {'a': 10, 'b': 20}
Because args
is a tuple and kwargs
is a dictionary, you can use all the usual methods like len(args)
or kwargs.get('key')
to safely handle the values inside.
Conclusion: Why This Matters
Variable packing is a fundamental Python idiom that allows you to write functions that are more dynamic, flexible, and robust. It's the key to making your code DRY (Don't Repeat Yourself) by allowing a single function to handle a wide range of inputs without needing endless if/else
statements or boilerplate code. In the real world, *args
and **kwargs
are widely used in frameworks like Django and FastAPI to create highly configurable functions and reusable components. Mastering this simple pattern is a crucial step toward writing truly professional, future-proof code.
Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.
Top comments (0)