Flexible Functions: *args and **kwargs
- We can use the syntax
*args and **kwargs to accept a variable number of both positional and keyword arguments.
def example_function(*args, **kwargs):
print(f"Positional args: {args}")
print(f"Keyword args: {kwargs}")
example_function(1, 2, 3, a="Value", b=True)
*args in Definition: Collecting Positionals
- Uses
*args to gather extra positional parameters into a tuple
- Allows functions to accept any number of positional inputs
- Common in utilities like custom logging or aggregation functions
def apply_operator(operator, *operands):
"""Applies operator to a variable number of operands. Supports 'add' and 'mul'.
Args:
operator (str): The operator to apply. Must be either 'add' or 'mul'.
*operands (int or float): Zero or more numbers to be combined.
Returns:
int or float: The result of applying the operator on the operands.
Raises:
ValueError: Raised when operator is not 'add' nor 'mul'.
"""
if operator == 'add':
result = sum(operands)
elif operator == 'mul':
result = 1
for n in operands:
result *= n
else:
raise ValueError(f"Unknown operator {operator}. Supported values are 'add' and 'mul'")
return result
print(apply_operator('add', 1, 2, 3, 4))
print(apply_operator('add', 1, 2, 3, 4, 5, 6, 7))
print(apply_operator('add', 1, 2))
print(apply_operator('mul', 1, 2, 3, 4))
print(apply_operator('mul', 1, 2, 3, 4, 5, 6, 7))
print(apply_operator('mul', 1, 2))
# print(apply_operator('div', 1, 2)) # Uncommenting raises ValueError since div is not supported
**kwargs in Definition: Collecting Keywords
- Uses
**kwargs to gather extra named parameters into a dictionary
- Ideal for optional configuration flags or settings
- Enables functions to accept flexible keyword arguments without predefining them
def set_options(**settings):
print(f"Received dictionary: {settings}")
for key, value in settings.items():
print(f"\t{key} = {value}")
set_options(timeout=30, user="admin", retries=5)
Order in Definition Matters
- Standard positional parameters must come first, some might also have a default value
- Followed by
*args to catch extra positionals
- Then keyword-only parameters, some might also have a default value
- Finally
**kwargs to catch extra keyword arguments
def process_request(url, method="GET", *headers, timeout, **params):
print(f"url={url}, method={method}, timeout={timeout}")
print(f"headers={headers}")
print(f"params={params}")
process_request("https://www.example.com", timeout=30)
process_request("https://www.example.com", "PUT", timeout=30)
# Equivalent to call above
process_request("https://www.example.com", timeout=30, method="PUT")
process_request(
"https://www.example.com",
"PUT",
"Auth: xyz",
"Content-Type: application/json",
timeout=30
)
process_request(
"https://www.example.com",
"PUT",
"Auth: xyz",
"Content-Type: application/json",
timeout=30,
retries=5,
log_level="DEBUG"
)
* in Call: Unpacking Positional Arguments
- Uses
*sequence to expand a list or tuple into positional arguments
- Sequence length must match the function’s positional parameters
- Useful for dynamic argument lists built at runtime
def connect(host, port, timeout):
print(f"Connecting to {host}:{port} with timeout {timeout}s.")
params = ["db.internal", 5432, 10]
params_with_extra_values = ["db.internal", 5432, 10, "a", True]
connect(*params)
connect(*params_with_extra_values[:3])
** in Call: Unpacking Keyword Arguments
- Uses
**dict to expand key-value pairs into keyword arguments
- Dictionary keys must match the function’s parameter names
- Common in configuration-driven function calls
def configure_service(name, version, replicas=1):
print(f"Setting up {name} v{version} with {replicas} replicas...")
config = {"name": "auth-service", "version": "2.1.0", "replicas": 3}
configure_service(**config)