Python by example

Args and Kwargs in Python

*args and **kwargs allow functions to accept a variable number of arguments. This makes functions more flexible by letting them handle different numbers of parameters.

*args (Variable Positional Arguments)

*args allows a function to accept any number of positional arguments. The arguments are passed as a tuple:

def sum_all(*args):
    total = 0
    for num in args:
        total += num
    return total

print(sum_all(1, 2, 3))        # Output: 6
print(sum_all(1, 2, 3, 4, 5))  # Output: 15
print(sum_all(10))             # Output: 10

**kwargs (Variable Keyword Arguments)

**kwargs allows a function to accept any number of keyword arguments. The arguments are passed as a dictionary:

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="New York")
# Output:
# name: Alice
# age: 30
# city: New York

Combining Regular Parameters with *args and **kwargs

You can mix regular parameters, *args, and **kwargs in the same function:

def greet(greeting, *names, **options):
    message = greeting
    if names:
        message += " " + ", ".join(names)
    if options.get("excited"):
        message += "!"
    if options.get("uppercase"):
        message = message.upper()
    return message

print(greet("Hello", "Alice", "Bob"))  # Output: Hello Alice, Bob
print(greet("Hi", "Charlie", excited=True))  # Output: Hi Charlie!
print(greet("Hey", uppercase=True))  # Output: HEY

Unpacking Arguments

You can also use * and ** to unpack arguments when calling functions:

def multiply(a, b, c):
    return a * b * c

# Unpacking a list with *
numbers = [2, 3, 4]
result = multiply(*numbers)  # Same as multiply(2, 3, 4)
print(result)  # Output: 24

# Unpacking a dictionary with **
def introduce(name, age, city):
    return f"My name is {name}, I'm {age} years old, and I live in {city}"

person = {"name": "Alice", "age": 25, "city": "Boston"}
print(introduce(**person))  # Output: My name is Alice, I'm 25 years old, and I live in Boston

Practical Examples

Function with Default Values and Variable Arguments

def create_user(username, email, *groups, **preferences):
    user = {
        "username": username,
        "email": email,
        "groups": list(groups),
        "preferences": preferences
    }
    return user

user = create_user("alice", "alice@email.com", "admin", "editor", 
                   theme="dark", notifications=True)
print(user)
# Output: {'username': 'alice', 'email': 'alice@email.com', 
#          'groups': ['admin', 'editor'], 
#          'preferences': {'theme': 'dark', 'notifications': True}}

Wrapper Functions

def log_function_call(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"Result: {result}")
        return result
    return wrapper

@log_function_call
def add(a, b):
    return a + b

add(3, 5)
# Output:
# Calling add with args: (3, 5), kwargs: {}
# Result: 8

Flexible Configuration Function

def configure_server(host, port=8000, *middlewares, **settings):
    config = {
        "host": host,
        "port": port,
        "middlewares": list(middlewares),
        "settings": settings
    }
    return config

config = configure_server("localhost", 3000, "auth", "cors", 
                         debug=True, timeout=30)
print(config)
# Output: {'host': 'localhost', 'port': 3000, 
#          'middlewares': ['auth', 'cors'], 
#          'settings': {'debug': True, 'timeout': 30}}

Order Matters

When using all parameter types together, they must be in this specific order:

def example_function(required_arg, default_arg="default", *args, **kwargs):
    print(f"Required: {required_arg}")
    print(f"Default: {default_arg}")
    print(f"Args: {args}")
    print(f"Kwargs: {kwargs}")

example_function("hello", "world", 1, 2, 3, key="value")
# Output:
# Required: hello
# Default: world
# Args: (1, 2, 3)
# Kwargs: {'key': 'value'}
Next example: Random Numbers