|$ curl https://forge-ai.dev/api/markdown?path=docs/python/functions
$cat docs/python-—-functions.md
updated Recently·18 min read·published
Python — Functions
◆Python◆Intermediate
Introduction
Functions are first-class citizens in Python. They can be assigned to variables, passed as arguments, returned from other functions, and define other functions.
Defining Functions
defining.py
Python
| 1 | # Basic syntax |
| 2 | def greet(name: str) -> str: |
| 3 | """Return a greeting message.""" |
| 4 | return f"Hello, {name}!" |
| 5 | |
| 6 | # Functions without return — return None |
| 7 | def log(message: str): |
| 8 | print(f"[LOG] {message}") |
| 9 | # implicit return None |
| 10 | |
| 11 | # Multiple return values (actually a tuple) |
| 12 | def min_max(items: list) -> tuple: |
| 13 | return min(items), max(items) |
| 14 | |
| 15 | low, high = min_max([3, 1, 4, 1, 5]) |
| 16 | print(low, high) # → 1 5 |
| 17 | |
| 18 | # Docstrings — first statement in function body |
| 19 | def complex_function(): |
| 20 | """Three levels of docstring detail: |
| 21 | |
| 22 | Single line describes purpose. |
| 23 | |
| 24 | Multi-line adds: |
| 25 | - Args: parameter descriptions |
| 26 | - Returns: return value description |
| 27 | - Raises: exception documentation |
| 28 | """ |
| 29 | pass |
Parameter Types
Python has the most flexible parameter system of any mainstream language. The order is always: positional, keyword-only, and variable-length.
parameters.py
Python
| 1 | # 1. Positional parameters |
| 2 | def f(a, b, c): |
| 3 | return a + b + c |
| 4 | |
| 5 | f(1, 2, 3) # positional |
| 6 | f(a=1, c=3, b=2) # keyword (order doesn't matter) |
| 7 | f(1, b=2, c=3) # mixed (positional first) |
| 8 | |
| 9 | # 2. Default values |
| 10 | def greet(name, greeting="Hello", punctuation="!"): |
| 11 | return f"{greeting}, {name}{punctuation}" |
| 12 | |
| 13 | print(greet("Alice")) # → Hello, Alice! |
| 14 | print(greet("Bob", "Hi")) # → Hi, Bob! |
| 15 | print(greet("Carol", punctuation="?")) # → Hello, Carol? |
| 16 | |
| 17 | # WARNING: mutable default values are evaluated once |
| 18 | def bad_append(item, target=[]): # BAD: same list every call |
| 19 | target.append(item) |
| 20 | return target |
| 21 | |
| 22 | print(bad_append(1)) # → [1] |
| 23 | print(bad_append(2)) # → [1, 2] — NOT [2]! |
| 24 | |
| 25 | def good_append(item, target=None): # GOOD |
| 26 | if target is None: |
| 27 | target = [] |
| 28 | target.append(item) |
| 29 | return target |
| 30 | |
| 31 | # 3. *args — variable positional arguments |
| 32 | def sum_all(*args: int) -> int: |
| 33 | return sum(args) |
| 34 | |
| 35 | print(sum_all(1, 2, 3, 4)) # → 10 |
| 36 | |
| 37 | # 4. **kwargs — variable keyword arguments |
| 38 | def build_url(**kwargs: str) -> str: |
| 39 | query = "&".join(f"{k}={v}" for k, v in kwargs.items()) |
| 40 | return f"/api?{query}" |
| 41 | |
| 42 | print(build_url(page="1", limit="10")) |
| 43 | # → /api?page=1&limit=10 |
| 44 | |
| 45 | # 5. Keyword-only arguments (after *, or *args) |
| 46 | def configure(*, host: str, port: int = 8080): |
| 47 | print(f"{host}:{port}") |
| 48 | |
| 49 | configure(host="localhost", port=3000) # OK |
| 50 | # configure("localhost", 3000) # TypeError! |
| 51 | |
| 52 | # 6. Positional-only arguments (Python 3.8+) |
| 53 | def divide(a: int, b: int, /) -> float: |
| 54 | """a and b must be provided positionally.""" |
| 55 | return a / b |
| 56 | |
| 57 | # 7. Combined (full signature) |
| 58 | def full_sig( |
| 59 | a, b, # positional-only if after / |
| 60 | /, # end of positional-only |
| 61 | c, d, # positional-or-keyword |
| 62 | *, # start of keyword-only |
| 63 | e, f # keyword-only |
| 64 | ): |
| 65 | pass |
Scope & Closure
scoping.py
Python
| 1 | # LEGB rule: Local → Enclosing → Global → Built-in |
| 2 | |
| 3 | x = "global" # global scope |
| 4 | |
| 5 | def outer(): |
| 6 | x = "enclosing" # enclosing scope |
| 7 | |
| 8 | def inner(): |
| 9 | x = "local" # local scope |
| 10 | print(x) |
| 11 | |
| 12 | inner() |
| 13 | print(x) |
| 14 | |
| 15 | outer() |
| 16 | print(x) |
| 17 | # Output: |
| 18 | # local |
| 19 | # enclosing |
| 20 | # global |
| 21 | |
| 22 | # The nonlocal keyword |
| 23 | def counter(): |
| 24 | count = 0 |
| 25 | def increment(): |
| 26 | nonlocal count # bind to enclosing scope |
| 27 | count += 1 |
| 28 | return count |
| 29 | return increment |
| 30 | |
| 31 | c = counter() |
| 32 | print(c()) # → 1 |
| 33 | print(c()) # → 2 |
| 34 | |
| 35 | # The global keyword |
| 36 | def set_global(): |
| 37 | global x |
| 38 | x = "modified" |
| 39 | |
| 40 | set_global() |
| 41 | print(x) # → modified |
| 42 | |
| 43 | # Closures — functions that capture their environment |
| 44 | def make_multiplier(factor: int): |
| 45 | def multiply(x: int) -> int: |
| 46 | return x * factor |
| 47 | return multiply |
| 48 | |
| 49 | double = make_multiplier(2) |
| 50 | triple = make_multiplier(3) |
| 51 | print(double(5)) # → 10 |
| 52 | print(triple(5)) # → 15 |
| 53 | # Each closure remembers its own factor |
Lambda Functions
Lambdas are anonymous, single-expression functions. They are useful for short operations where a full def would be verbose.
lambda.py
Python
| 1 | # Syntax: lambda args: expression |
| 2 | square = lambda x: x ** 2 |
| 3 | print(square(5)) # → 25 |
| 4 | |
| 5 | # Common use: sorting with custom key |
| 6 | pairs = [(1, "b"), (3, "a"), (2, "c")] |
| 7 | pairs.sort(key=lambda x: x[1]) # sort by second element |
| 8 | print(pairs) # → [(3, 'a'), (1, 'b'), (2, 'c')] |
| 9 | |
| 10 | # With map, filter, reduce (comprehensions are more Pythonic) |
| 11 | nums = [1, 2, 3, 4, 5] |
| 12 | squares = list(map(lambda x: x ** 2, nums)) |
| 13 | evens = list(filter(lambda x: x % 2 == 0, nums)) |
| 14 | |
| 15 | # But prefer comprehensions: |
| 16 | squares = [x ** 2 for x in nums] |
| 17 | evens = [x for x in nums if x % 2 == 0] |
| 18 | |
| 19 | from functools import reduce |
| 20 | total = reduce(lambda a, b: a + b, nums) # sum via reduce |
| 21 | |
| 22 | # Limitations: single expression only, no statements |
| 23 | # Can't do: lambda x: if x > 0: return x # SyntaxError |
| 24 | # Use a regular def instead |
ℹ
info
Prefer comprehensions over map and filter — they are more readable. Lambdas shine in places like sort(key=...) and callback arguments.
Higher-Order Functions
Functions that take or return other functions are called higher-order functions. They are the foundation of decorators and functional programming.
higher_order.py
Python
| 1 | # Functions as arguments |
| 2 | def apply_twice(func, arg): |
| 3 | return func(func(arg)) |
| 4 | |
| 5 | def add_one(x): |
| 6 | return x + 1 |
| 7 | |
| 8 | print(apply_twice(add_one, 5)) # → 7 |
| 9 | |
| 10 | # Functions as return values |
| 11 | def get_operation(op: str): |
| 12 | def add(a, b): return a + b |
| 13 | def multiply(a, b): return a * b |
| 14 | |
| 15 | if op == "+": |
| 16 | return add |
| 17 | elif op == "*": |
| 18 | return multiply |
| 19 | raise ValueError(f"Unknown op: {op}") |
| 20 | |
| 21 | add_func = get_operation("+") |
| 22 | print(add_func(3, 4)) # → 7 |
| 23 | |
| 24 | # functools.partial — pre-fill arguments |
| 25 | from functools import partial |
| 26 | |
| 27 | def power(base, exp): |
| 28 | return base ** exp |
| 29 | |
| 30 | square = partial(power, exp=2) |
| 31 | cube = partial(power, exp=3) |
| 32 | print(square(5)) # → 25 |
| 33 | print(cube(2)) # → 8 |
| 34 | |
| 35 | # Key functions in functools |
| 36 | from functools import wraps, lru_cache, singledispatch |
| 37 | |
| 38 | # lru_cache — memoization |
| 39 | @lru_cache(maxsize=128) |
| 40 | def fibonacci(n): |
| 41 | if n < 2: |
| 42 | return n |
| 43 | return fibonacci(n - 1) + fibonacci(n - 2) |
| 44 | |
| 45 | # singledispatch — function overloading by type |
| 46 | @singledispatch |
| 47 | def serialize(obj): |
| 48 | raise NotImplementedError |
| 49 | |
| 50 | @serialize.register(int) |
| 51 | def _(obj): |
| 52 | return str(obj) |
| 53 | |
| 54 | @serialize.register(list) |
| 55 | def _(obj): |
| 56 | return ",".join(str(x) for x in obj) |
Type Annotations
annotations.py
Python
| 1 | # Basic annotations |
| 2 | def add(a: int, b: int) -> int: |
| 3 | return a + b |
| 4 | |
| 5 | # Union — multiple possible types |
| 6 | from typing import Union |
| 7 | def parse_int(value: Union[str, int]) -> int: |
| 8 | return int(value) |
| 9 | |
| 10 | # Optional — short for Union[T, None] |
| 11 | from typing import Optional |
| 12 | def find_user(id: int) -> Optional[dict]: |
| 13 | if id == 1: |
| 14 | return {"name": "Alice"} |
| 15 | return None |
| 16 | |
| 17 | # Generic containers (Python 3.9+ — use built-ins) |
| 18 | def process(items: list[int]) -> list[str]: |
| 19 | return [str(x) for x in items] |
| 20 | |
| 21 | # Callable |
| 22 | from typing import Callable |
| 23 | def run_callback(cb: Callable[[int, int], int]) -> int: |
| 24 | return cb(1, 2) |
| 25 | |
| 26 | # TypeVar — generics |
| 27 | from typing import TypeVar |
| 28 | T = TypeVar("T") |
| 29 | |
| 30 | def first(items: list[T]) -> T | None: |
| 31 | return items[0] if items else None |
| 32 | |
| 33 | # Python 3.12+ concise generics |
| 34 | def last[T](items: list[T]) -> T | None: |
| 35 | return items[-1] if items else None |
$Blueprint — Engineering Documentation·Section ID: PYTHON-FUNC·Revision: 1.0