Python - Function Arguments (args, kwargs)
• Python supports four types of function arguments: positional, keyword, variable positional (*args), and variable keyword (**kwargs), each serving distinct use cases in API design and code...
Key Insights
• Python supports four types of function arguments: positional, keyword, variable positional (*args), and variable keyword (**kwargs), each serving distinct use cases in API design and code flexibility. • *args collects extra positional arguments into a tuple, while **kwargs collects extra keyword arguments into a dictionary, enabling functions to accept arbitrary numbers of parameters. • Argument order matters: positional arguments must come before *args, which must come before keyword arguments, which must come before **kwargs.
Positional and Keyword Arguments
Python functions accept arguments in two fundamental ways: positional and keyword. Positional arguments rely on order, while keyword arguments use explicit names.
def create_user(username, email, age):
return {
'username': username,
'email': email,
'age': age
}
# Positional arguments
user1 = create_user('john_doe', 'john@example.com', 30)
# Keyword arguments
user2 = create_user(email='jane@example.com', username='jane_doe', age=25)
# Mixed (positional must come first)
user3 = create_user('bob', email='bob@example.com', age=35)
Default values make arguments optional:
def create_user(username, email, age=18, active=True):
return {
'username': username,
'email': email,
'age': age,
'active': active
}
user = create_user('alice', 'alice@example.com')
# {'username': 'alice', 'email': 'alice@example.com', 'age': 18, 'active': True}
Variable Positional Arguments (*args)
The *args syntax collects any number of positional arguments into a tuple. This enables functions to accept flexible argument counts without defining each parameter explicitly.
def calculate_sum(*args):
total = 0
for num in args:
total += num
return total
print(calculate_sum(1, 2, 3)) # 6
print(calculate_sum(10, 20, 30, 40, 50)) # 150
*args works alongside regular parameters:
def log_message(level, *messages):
timestamp = "2024-01-01 12:00:00"
combined = " ".join(str(msg) for msg in messages)
print(f"[{timestamp}] {level}: {combined}")
log_message("ERROR", "Database", "connection", "failed")
# [2024-01-01 12:00:00] ERROR: Database connection failed
Real-world example with data processing:
def merge_datasets(*datasets):
merged = []
for dataset in datasets:
merged.extend(dataset)
return merged
data1 = [1, 2, 3]
data2 = [4, 5, 6]
data3 = [7, 8, 9]
result = merge_datasets(data1, data2, data3)
print(result) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Variable Keyword Arguments (**kwargs)
The **kwargs syntax collects keyword arguments into a dictionary. The parameter name becomes the key, and the argument value becomes the dictionary value.
def create_config(**kwargs):
config = {}
for key, value in kwargs.items():
config[key] = value
return config
settings = create_config(
host='localhost',
port=5432,
database='mydb',
timeout=30
)
print(settings)
# {'host': 'localhost', 'port': 5432, 'database': 'mydb', 'timeout': 30}
**kwargs enables flexible API design:
def build_query(table, **conditions):
query = f"SELECT * FROM {table}"
if conditions:
where_clauses = [f"{key} = '{value}'" for key, value in conditions.items()]
query += " WHERE " + " AND ".join(where_clauses)
return query
query1 = build_query('users', status='active', role='admin')
# SELECT * FROM users WHERE status = 'active' AND role = 'admin'
query2 = build_query('products', category='electronics', price=999)
# SELECT * FROM products WHERE category = 'electronics' AND price = '999'
Combining All Argument Types
Python enforces a specific order when combining argument types: positional, *args, keyword, **kwargs.
def complex_function(pos1, pos2, *args, key1='default', key2='default', **kwargs):
print(f"Positional: {pos1}, {pos2}")
print(f"Args: {args}")
print(f"Keywords: {key1}, {key2}")
print(f"Kwargs: {kwargs}")
complex_function(
1, 2, # pos1, pos2
3, 4, 5, # args
key1='custom', # keyword argument
extra1='a', # kwargs
extra2='b' # kwargs
)
# Positional: 1, 2
# Args: (3, 4, 5)
# Keywords: custom, default
# Kwargs: {'extra1': 'a', 'extra2': 'b'}
Practical example with HTTP request wrapper:
def make_request(method, url, *middleware, timeout=30, retry=3, **headers):
request = {
'method': method,
'url': url,
'timeout': timeout,
'retry': retry,
'middleware': middleware,
'headers': headers
}
return request
request = make_request(
'POST',
'https://api.example.com/users',
'auth_middleware',
'logging_middleware',
timeout=60,
Authorization='Bearer token123',
Content_Type='application/json'
)
Unpacking Arguments
The * and ** operators also unpack sequences and dictionaries when calling functions.
def create_point(x, y, z):
return {'x': x, 'y': y, 'z': z}
coordinates = [10, 20, 30]
point = create_point(*coordinates)
print(point) # {'x': 10, 'y': 20, 'z': 30}
params = {'x': 5, 'y': 15, 'z': 25}
point2 = create_point(**params)
print(point2) # {'x': 5, 'y': 15, 'z': 25}
Combining unpacking with additional arguments:
def configure_server(host, port, *features, **settings):
return {
'host': host,
'port': port,
'features': features,
'settings': settings
}
base_config = ['ssl', 'compression']
extra_settings = {'max_connections': 100, 'timeout': 30}
server = configure_server(
'localhost',
8080,
*base_config,
'caching',
**extra_settings,
debug=True
)
# {
# 'host': 'localhost',
# 'port': 8080,
# 'features': ('ssl', 'compression', 'caching'),
# 'settings': {'max_connections': 100, 'timeout': 30, 'debug': True}
# }
Enforcing Argument Types
Python 3 introduced positional-only (/) and keyword-only (*) parameters for stricter API contracts.
def create_user(username, /, email, *, role='user', active=True):
return {
'username': username,
'email': email,
'role': role,
'active': active
}
# Valid calls
user1 = create_user('john', 'john@example.com')
user2 = create_user('jane', email='jane@example.com', role='admin')
# Invalid: username must be positional
# user3 = create_user(username='bob', email='bob@example.com') # TypeError
# Invalid: role must be keyword
# user4 = create_user('alice', 'alice@example.com', 'admin') # TypeError
This pattern prevents API misuse and improves code clarity:
def process_payment(amount, /, payment_method, *, currency='USD', fee=0):
total = amount + fee
return {
'amount': amount,
'method': payment_method,
'currency': currency,
'total': total
}
# Clear, unambiguous calls
payment = process_payment(100.00, 'credit_card', currency='EUR', fee=2.50)
Common Patterns and Best Practices
Decorator pattern with *args and **kwargs:
def timer_decorator(func):
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timer_decorator
def process_data(data, iterations=1000):
for _ in range(iterations):
_ = [x * 2 for x in data]
return "Done"
process_data([1, 2, 3, 4, 5], iterations=5000)
Builder pattern for complex object creation:
class DatabaseConnection:
def __init__(self, host, port, **options):
self.host = host
self.port = port
self.pool_size = options.get('pool_size', 10)
self.timeout = options.get('timeout', 30)
self.ssl = options.get('ssl', False)
self.retry_attempts = options.get('retry_attempts', 3)
def __repr__(self):
return f"DB({self.host}:{self.port}, pool={self.pool_size})"
db = DatabaseConnection(
'localhost',
5432,
pool_size=20,
ssl=True,
timeout=60
)
Use *args and **kwargs when building flexible, extensible APIs. Use positional-only and keyword-only parameters when you need strict contracts. Always document expected argument types and behaviors for maintainable code.