Advanced Functions: Args, Kwargs, and Function Flexibility
Welcome to advanced function techniques! Now that you know the basics, let’s make functions that can handle any number of arguments and be incredibly flexible.
Variable-Length Arguments (*args)
The Problem: Fixed Number of Parameters
# ❌ Limited function - only works with exactly 2 numbers
def add_two_numbers(a, b):
return a + b
print(add_two_numbers(5, 3)) # 8
# add_two_numbers(1, 2, 3) # Error: too many arguments!
The Solution: *args
# ✅ Flexible function - works with any number of arguments
def add_numbers(*args):
"""Add any number of numbers together."""
total = 0
for number in args:
total += number
return total
print(add_numbers(5, 3)) # 8
print(add_numbers(1, 2, 3, 4, 5)) # 15
print(add_numbers(10)) # 10
print(add_numbers()) # 0
Real-Life Example: Pizza Toppings
def make_pizza(size, *toppings):
"""Make a pizza with any number of toppings."""
print(f"Making a {size} pizza with the following toppings:")
if not toppings:
print(" - Just cheese")
else:
for topping in toppings:
print(f" - {topping}")
print("🍕 Pizza is ready!")
# Different pizza orders
make_pizza("large", "pepperoni", "mushrooms", "olives")
make_pizza("medium", "cheese") # No extra toppings
make_pizza("small", "hawaiian", "extra cheese", "pineapple", "ham")
*args is a Tuple
def print_args_info(*args):
"""Demonstrate that *args is a tuple."""
print(f"Type of args: {type(args)}")
print(f"Args content: {args}")
print(f"Number of args: {len(args)}")
if args:
print(f"First arg: {args[0]}")
print(f"Last arg: {args[-1]}")
print_args_info(1, 2, 3, "hello", True)
Keyword Arguments (**kwargs)
The Problem: Too Many Optional Parameters
# ❌ Hard to maintain with many optional parameters
def create_user(name, age=None, email=None, phone=None, address=None):
user = {"name": name}
if age: user["age"] = age
if email: user["email"] = email
if phone: user["phone"] = phone
if address: user["address"] = address
return user
# Usage is okay, but adding new fields requires changing the function
user1 = create_user("Alice", age=25, email="alice@email.com")
The Solution: **kwargs
# ✅ Flexible - can add any number of keyword arguments
def create_user(name, **kwargs):
"""Create a user with any additional attributes."""
user = {"name": name}
user.update(kwargs) # Add all keyword arguments
return user
# Now you can add any fields without changing the function!
user1 = create_user("Alice", age=25, email="alice@email.com")
user2 = create_user("Bob", age=30, email="bob@email.com", phone="555-0123", department="Engineering")
user3 = create_user("Charlie", hobbies=["reading", "coding"], favorite_color="blue")
print(user1)
print(user2)
print(user3)
Real-Life Example: Custom Sandwich Maker
def make_sandwich(bread_type, **ingredients):
"""Make a custom sandwich with any ingredients."""
print(f"🍞 Making a {bread_type} sandwich with:")
if not ingredients:
print(" - Just bread (very plain!)")
else:
for ingredient, amount in ingredients.items():
print(f" - {amount} {ingredient}")
print("🥪 Sandwich complete!")
# Different sandwich orders
make_sandwich("whole wheat", lettuce="leafy greens", tomato="slices", cheese="cheddar", mayo="spread")
make_sandwich("sourdough", turkey="slices", avocado="mashed", bacon="strips")
make_sandwich("rye") # Plain sandwich
**kwargs is a Dictionary
def print_kwargs_info(**kwargs):
"""Demonstrate that **kwargs is a dictionary."""
print(f"Type of kwargs: {type(kwargs)}")
print(f"Kwargs content: {kwargs}")
print(f"Number of kwargs: {len(kwargs)}")
for key, value in kwargs.items():
print(f" {key}: {value}")
print_kwargs_info(name="Alice", age=25, city="New York", hobbies=["reading", "coding"])
Combining Regular Parameters with *args and **kwargs
Order Matters!
# ✅ Correct order: required → *args → **kwargs
def flexible_function(required_param, *args, **kwargs):
print(f"Required: {required_param}")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
flexible_function("hello", 1, 2, 3, name="Alice", age=25)
# ❌ Wrong order - this will cause a SyntaxError
# def wrong_order(*args, required_param, **kwargs):
# pass
Real-Life Example: Order Food
def order_food(main_dish, *side_dishes, **customizations):
"""Place a food order with customizations."""
print(f"🍽️ Ordering {main_dish}")
if side_dishes:
print("Side dishes:")
for side in side_dishes:
print(f" - {side}")
if customizations:
print("Customizations:")
for item, instruction in customizations.items():
print(f" - {item}: {instruction}")
print("Order placed! ✅")
# Different orders
order_food("steak", "mashed potatoes", "green beans",
steak="medium rare", potatoes="extra butter")
order_food("salad", "breadsticks",
dressing="ranch", extra_cheese=True)
order_food("pasta") # Simple order
Unpacking Arguments
Unpacking Lists/Tuples with *
def add_three_numbers(a, b, c):
return a + b + c
# Regular call
result1 = add_three_numbers(1, 2, 3)
print(result1) # 6
# Unpack a list/tuple
numbers = [4, 5, 6]
result2 = add_three_numbers(*numbers) # Unpacks to: add_three_numbers(4, 5, 6)
print(result2) # 15
# Unpack a tuple
coords = (10, 20, 30)
result3 = add_three_numbers(*coords)
print(result3) # 60
Unpacking Dictionaries with **
def create_profile(name, age, city, profession):
return f"{name} is {age} years old, lives in {city}, and works as a {profession}."
# Regular call
profile1 = create_profile("Alice", 25, "New York", "engineer")
print(profile1)
# Unpack a dictionary
person = {
"name": "Bob",
"age": 30,
"city": "London",
"profession": "designer"
}
profile2 = create_profile(**person) # Unpacks to keyword arguments
print(profile2)
Real-Life Example: Database Query Builder
def build_query(table, **filters):
"""Build a database query with filters."""
query = f"SELECT * FROM {table}"
if filters:
conditions = []
for field, value in filters.items():
conditions.append(f"{field} = '{value}'")
query += " WHERE " + " AND ".join(conditions)
return query
# Build different queries
query1 = build_query("users", active=True, role="admin")
print(query1) # "SELECT * FROM users WHERE active = 'True' AND role = 'admin'"
query2 = build_query("products", category="electronics", in_stock=True)
print(query2) # "SELECT * FROM products WHERE category = 'electronics' AND in_stock = 'True'"
query3 = build_query("orders") # No filters
print(query3) # "SELECT * FROM orders"
Advanced Examples
Example 1: Flexible Calculator
def calculator(operation, *numbers, **options):
"""Perform calculations with flexible arguments."""
if not numbers:
return "Error: No numbers provided"
result = numbers[0]
# Apply operation to all numbers
for num in numbers[1:]:
if operation == "add":
result += num
elif operation == "multiply":
result *= num
elif operation == "subtract":
result -= num
elif operation == "divide":
if num == 0:
return "Error: Division by zero"
result /= num
else:
return f"Error: Unknown operation '{operation}'"
# Apply options
if "round" in options:
result = round(result, options["round"])
if "absolute" in options and options["absolute"]:
result = abs(result)
return result
# Test the calculator
print(calculator("add", 1, 2, 3, 4, 5)) # 15
print(calculator("multiply", 2, 3, 4)) # 24
print(calculator("divide", 100, 2, 2, round=2)) # 25.0
print(calculator("subtract", 100, 20, 30, absolute=True)) # 50
Example 2: HTML Tag Generator
def create_html_tag(tag_name, content="", **attributes):
"""Create an HTML tag with attributes."""
# Build attribute string
attr_string = ""
if attributes:
attr_list = []
for attr, value in attributes.items():
attr_list.append(f'{attr}="{value}"')
attr_string = " " + " ".join(attr_list)
# Create the tag
if content:
html = f"<{tag_name}{attr_string}>{content}</{tag_name}>"
else:
html = f"<{tag_name}{attr_string} />"
return html
# Create different HTML elements
div = create_html_tag("div", "Hello World", class_="greeting", id="main")
print(div) # <div class="greeting" id="main">Hello World</div>
link = create_html_tag("a", "Click me", href="https://example.com", target="_blank")
print(link) # <a href="https://example.com" target="_blank">Click me</a>
img = create_html_tag("img", src="image.jpg", alt="A picture", width="300")
print(img) # <img src="image.jpg" alt="A picture" width="300" />
Example 3: Logger Function
def log_message(level, message, *args, **context):
"""Log a message with flexible formatting and context."""
# Format the message with args if provided
if args:
message = message.format(*args)
# Build log entry
timestamp = "2024-01-15 10:30:00" # In real code, use datetime
log_entry = f"[{timestamp}] {level.upper()}: {message}"
# Add context information
if context:
context_info = ", ".join(f"{k}={v}" for k, v in context.items())
log_entry += f" ({context_info})"
print(log_entry)
return log_entry
# Different log messages
log_message("info", "User {} logged in", "alice123", user_id=123, ip="192.168.1.1")
log_message("error", "Database connection failed", attempt=3, error_code="ECONNREFUSED")
log_message("debug", "Processing completed", duration="2.5s", records_processed=1500)
Function Introspection
Inspecting Function Signatures
import inspect
def example_function(a, b=10, *args, **kwargs):
"""An example function with various parameter types."""
pass
# Get function signature
sig = inspect.signature(example_function)
print(f"Function signature: {sig}")
# Get parameters
params = sig.parameters
print("Parameters:")
for name, param in params.items():
print(f" {name}: {param}")
Best Practices
1. Use Descriptive Parameter Names
# ❌ Not descriptive
def calc(x, y, z):
pass
# ✅ Descriptive
def calculate_total(price, tax_rate, discount_percent):
pass
2. Document Your Functions
def process_data(data, *filters, **options):
"""
Process data with optional filters and options.
Args:
data: The data to process
*filters: Variable number of filter functions to apply
**options: Keyword options for processing
Returns:
Processed data
Examples:
process_data(my_data, filter1, filter2, sort=True, reverse=False)
"""
pass
3. Validate Arguments
def divide_numbers(dividend, *divisors):
"""Divide a number by multiple divisors."""
if not divisors:
raise ValueError("At least one divisor required")
result = dividend
for divisor in divisors:
if divisor == 0:
raise ValueError("Cannot divide by zero")
result /= divisor
return result
# Test validation
try:
result = divide_numbers(100, 2, 5)
print(f"Result: {result}")
except ValueError as e:
print(f"Error: {e}")
Practice Exercises
Exercise 1: Flexible Shopping List
Create a shopping list function that accepts:
- Required: list name
- *args: items to add
- **kwargs: item quantities
Exercise 2: Custom Formatter
Create a text formatting function that accepts:
- Required: text to format
- *args: formatting operations (uppercase, lowercase, capitalize, etc.)
- **kwargs: additional formatting options (color, style, etc.)
Exercise 3: Database Query Builder
Create a query builder that accepts:
- Required: table name
- *args: columns to select
- **kwargs: WHERE conditions
Exercise 4: Report Generator
Create a report generator that accepts:
- Required: report title
- *args: data sections
- **kwargs: report options (format, include_charts, etc.)
Exercise 5: API Request Builder
Create an API request function that accepts:
- Required: endpoint URL
- *args: path parameters
- **kwargs: query parameters and headers
Summary
Advanced functions give you incredible flexibility:
*args: Accept any number of positional arguments (as a tuple)**kwargs: Accept any number of keyword arguments (as a dictionary)- Unpacking: Use
*and**to unpack iterables into arguments - Parameter order: required →
*args→**kwargs
These techniques allow you to create functions that can handle:
- Variable numbers of arguments
- Optional parameters without function changes
- Complex configuration options
- API-like interfaces
Next: Lambda Functions - anonymous functions for simple tasks! 🚀