Advanced Functions
Checking access...
Variable Arguments
# *args — arbitrary positional arguments (tuple)def sum_all(*args): return sum(args)
print(sum_all(1, 2, 3, 4)) # 10
# **kwargs — arbitrary keyword arguments (dict)def print_kwargs(**kwargs): for key, value in kwargs.items(): print(f"{key}: {value}")
print_kwargs(name="Alice", age=25, role="engineer")
# Combineddef complex_func(a, b, *args, **kwargs): print(f"Positional: {a}, {b}") print(f"Extra args: {args}") print(f"Extra kwargs: {kwargs}")
complex_func(1, 2, 3, 4, x=10, y=20)Unpacking Arguments
# Unpack list/tuple into argumentsdef add(a, b, c): return a + b + c
nums = [1, 2, 3]print(add(*nums)) # 6 — unpacks list
# Unpack dict into keyword argumentsparams = {"a": 10, "b": 20, "c": 30}print(add(**params)) # 60
# Combiningdef connect(host, port, user, password): print(f"{user}@{host}:{port}")
config = {"host": "localhost", "port": 5432}creds = {"user": "admin", "password": "secret"}connect(**config, **creds)Lambda Functions
Small anonymous functions — useful for short operations:
# Syntax: lambda args: expressionsquare = lambda x: x ** 2print(square(5)) # 25
# Common use: sorting with keystudents = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]sorted(students, key=lambda s: s[1]) # Sort by score# [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
# With map, filter, reducenumbers = [1, 2, 3, 4, 5]squares = list(map(lambda x: x ** 2, numbers))evens = list(filter(lambda x: x % 2 == 0, numbers))Tip
Lambda functions are limited to a single expression. For anything complex, use a regular def function.
Closures
A function that captures variables from its enclosing scope:
def make_multiplier(factor): """Return a function that multiplies by factor.""" def multiply(x): return x * factor return multiply
double = make_multiplier(2)triple = make_multiplier(3)
print(double(5)) # 10print(triple(5)) # 15Closure Use Case: Counter
def make_counter(): count = 0
def counter(): nonlocal count count += 1 return count
return counter
counter_a = make_counter()counter_b = make_counter()
print(counter_a()) # 1print(counter_a()) # 2print(counter_b()) # 1 — independent stateDecorators
Decorators wrap functions to add behavior:
# Basic decoratordef log_calls(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") result = func(*args, **kwargs) print(f"{func.__name__} returned {result}") return result return wrapper
@log_callsdef add(a, b): return a + b
add(3, 5)# Calling add# add returned 8Decorator with Arguments
def repeat(n): """Repeat function execution n times.""" def decorator(func): def wrapper(*args, **kwargs): for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator
@repeat(3)def say(message): print(message)
say("Hello!")# Hello!# Hello!# Hello!Practical Decorator: Timing
import time
def timer(func): """Time function execution.""" def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start print(f"{func.__name__} took {elapsed:.4f}s") return result return wrapper
@timerdef slow_function(): time.sleep(0.5) return "done"
slow_function()# slow_function took 0.5002sPreserving Function Metadata
from functools import wraps
def my_decorator(func): @wraps(func) # Preserves func.__name__, __doc__, etc. def wrapper(*args, **kwargs): print("Before") return func(*args, **kwargs) return wrapper
@my_decoratordef greet(name): """Greet someone.""" return f"Hello, {name}"
print(greet.__name__) # 'greet' (not 'wrapper' without @wraps)print(greet.__doc__) # 'Greet someone.'Key Takeaways
*argscollects extra positional args as a tuple;**kwargscollects keyword args as a dict- Unpack arguments with
*seqand**dictwhen calling functions - Lambdas:
lambda args: expression— single-expression anonymous functions - Closures capture enclosing scope variables; use
nonlocalto modify them - Decorators wrap functions; use
@wrapsto preserve metadata - Decorators can take arguments with triple nesting:
def decorator(args): return wrapper