PY

Functions

Python · 9 entries

Defining Functions

syntax
def name(params):
    """docstring"""
    return value
example
def greet(name: str, greeting: str = "Hello") -> str:
    """Build a personalized greeting."""
    return f"{greeting}, {name}!"

print(greet("Alice"))
print(greet("Bob", greeting="Hey"))
output
Hello, Alice!
Hey, Bob!

Note Functions without an explicit return statement return None. Docstrings are accessible via help(func) and func.__doc__.

*args and **kwargs

syntax
def func(*args, **kwargs):
example
def log_event(event: str, *tags: str, **metadata: str):
    print(f"Event: {event}")
    print(f"Tags: {tags}")
    print(f"Meta: {metadata}")

log_event("login", "auth", "user", ip="10.0.0.1")
output
Event: login
Tags: ('auth', 'user')
Meta: {'ip': '10.0.0.1'}

Note *args collects extra positional arguments as a tuple, **kwargs collects extra keyword arguments as a dict. They can coexist in the same signature.

Keyword-Only & Positional-Only Args

syntax
def func(pos_only, /, normal, *, kw_only):
example
def fetch(url: str, /, *, timeout: int = 30, retries: int = 3):
    print(f"GET {url} (timeout={timeout}, retries={retries})")

fetch("https://api.example.com", timeout=10)
# fetch(url="...")  # TypeError: positional-only
output
GET https://api.example.com (timeout=10, retries=3)

Note / marks preceding params as positional-only. * marks following params as keyword-only. This prevents callers from relying on internal parameter names.

Lambda (Anonymous Functions)

syntax
lambda params: expression
example
square = lambda n: n ** 2
print(square(5))

users = [{"name": "Carol", "age": 28}, {"name": "Alice", "age": 35}]
users.sort(key=lambda u: u["age"])
print([u["name"] for u in users])
output
25
['Carol', 'Alice']

Note Lambdas are limited to a single expression. For anything with statements or multiple lines, use a regular def. Assigning a lambda to a variable is less readable than def.

Decorators

syntax
@decorator
def func(): ...
example
import functools
import time

def timer(func):
    @functools.wraps(func)
    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

@timer
def compute(n):
    return sum(range(n))

compute(1_000_000)
output
compute took 0.0XXXs

Note Always use @functools.wraps(func) in your wrapper so that the decorated function preserves its name and docstring. Decorators run at import time.

Closures

syntax
def outer():
    captured = value
    def inner():
        return captured
    return inner
example
def make_multiplier(factor: int):
    def multiply(n: int) -> int:
        return n * factor
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10))
output
20 30

Note The inner function captures the enclosing variable by reference, not by value. See Common Mistakes for the late-binding closure gotcha.

Generators with yield

syntax
def gen():
    yield value
example
def countdown(n: int):
    while n > 0:
        yield n
        n -= 1

for tick in countdown(3):
    print(tick)

# Generators are lazy — values produced one at a time
nums = countdown(1_000_000)
print(next(nums))
output
3
2
1
1000000

Note Generators produce values lazily, consuming almost no memory regardless of size. They are single-use: once exhausted, they cannot be restarted.

Type Hints for Functions

syntax
def func(param: Type) -> ReturnType:
example
from collections.abc import Callable

def apply(
    values: list[int],
    transform: Callable[[int], int]
) -> list[int]:
    return [transform(v) for v in values]

result = apply([1, 2, 3], lambda x: x * 10)
print(result)
output
[10, 20, 30]

Note Use collections.abc.Callable for function type hints. The format is Callable[[ArgTypes], ReturnType]. For complex signatures, consider typing.Protocol.

Recursion

syntax
def func(n):
    if base_case: return value
    return func(modified_n)
example
def factorial(n: int) -> int:
    if n <= 1:
        return 1
    return n * factorial(n - 1)

print(factorial(6))

import sys
print(f"Recursion limit: {sys.getrecursionlimit()}")
output
720
Recursion limit: 1000

Note Python's default recursion limit is 1000. Deeply recursive algorithms should be rewritten iteratively or use sys.setrecursionlimit() with caution.