|$ curl https://forge-ai.dev/api/markdown?path=docs/python/functions
$cat docs/python-—-functions.md
updated Recently·18 min read·published

Python — Functions

PythonIntermediate
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
2def greet(name: str) -> str:
3 """Return a greeting message."""
4 return f"Hello, {name}!"
5
6# Functions without return — return None
7def log(message: str):
8 print(f"[LOG] {message}")
9 # implicit return None
10
11# Multiple return values (actually a tuple)
12def min_max(items: list) -> tuple:
13 return min(items), max(items)
14
15low, high = min_max([3, 1, 4, 1, 5])
16print(low, high) # → 1 5
17
18# Docstrings — first statement in function body
19def 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
2def f(a, b, c):
3 return a + b + c
4
5f(1, 2, 3) # positional
6f(a=1, c=3, b=2) # keyword (order doesn't matter)
7f(1, b=2, c=3) # mixed (positional first)
8
9# 2. Default values
10def greet(name, greeting="Hello", punctuation="!"):
11 return f"{greeting}, {name}{punctuation}"
12
13print(greet("Alice")) # → Hello, Alice!
14print(greet("Bob", "Hi")) # → Hi, Bob!
15print(greet("Carol", punctuation="?")) # → Hello, Carol?
16
17# WARNING: mutable default values are evaluated once
18def bad_append(item, target=[]): # BAD: same list every call
19 target.append(item)
20 return target
21
22print(bad_append(1)) # → [1]
23print(bad_append(2)) # → [1, 2] — NOT [2]!
24
25def 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
32def sum_all(*args: int) -> int:
33 return sum(args)
34
35print(sum_all(1, 2, 3, 4)) # → 10
36
37# 4. **kwargs — variable keyword arguments
38def build_url(**kwargs: str) -> str:
39 query = "&".join(f"{k}={v}" for k, v in kwargs.items())
40 return f"/api?{query}"
41
42print(build_url(page="1", limit="10"))
43# → /api?page=1&limit=10
44
45# 5. Keyword-only arguments (after *, or *args)
46def configure(*, host: str, port: int = 8080):
47 print(f"{host}:{port}")
48
49configure(host="localhost", port=3000) # OK
50# configure("localhost", 3000) # TypeError!
51
52# 6. Positional-only arguments (Python 3.8+)
53def divide(a: int, b: int, /) -> float:
54 """a and b must be provided positionally."""
55 return a / b
56
57# 7. Combined (full signature)
58def 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
3x = "global" # global scope
4
5def 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
15outer()
16print(x)
17# Output:
18# local
19# enclosing
20# global
21
22# The nonlocal keyword
23def counter():
24 count = 0
25 def increment():
26 nonlocal count # bind to enclosing scope
27 count += 1
28 return count
29 return increment
30
31c = counter()
32print(c()) # → 1
33print(c()) # → 2
34
35# The global keyword
36def set_global():
37 global x
38 x = "modified"
39
40set_global()
41print(x) # → modified
42
43# Closures — functions that capture their environment
44def make_multiplier(factor: int):
45 def multiply(x: int) -> int:
46 return x * factor
47 return multiply
48
49double = make_multiplier(2)
50triple = make_multiplier(3)
51print(double(5)) # → 10
52print(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
2square = lambda x: x ** 2
3print(square(5)) # → 25
4
5# Common use: sorting with custom key
6pairs = [(1, "b"), (3, "a"), (2, "c")]
7pairs.sort(key=lambda x: x[1]) # sort by second element
8print(pairs) # → [(3, 'a'), (1, 'b'), (2, 'c')]
9
10# With map, filter, reduce (comprehensions are more Pythonic)
11nums = [1, 2, 3, 4, 5]
12squares = list(map(lambda x: x ** 2, nums))
13evens = list(filter(lambda x: x % 2 == 0, nums))
14
15# But prefer comprehensions:
16squares = [x ** 2 for x in nums]
17evens = [x for x in nums if x % 2 == 0]
18
19from functools import reduce
20total = 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
2def apply_twice(func, arg):
3 return func(func(arg))
4
5def add_one(x):
6 return x + 1
7
8print(apply_twice(add_one, 5)) # → 7
9
10# Functions as return values
11def 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
21add_func = get_operation("+")
22print(add_func(3, 4)) # → 7
23
24# functools.partial — pre-fill arguments
25from functools import partial
26
27def power(base, exp):
28 return base ** exp
29
30square = partial(power, exp=2)
31cube = partial(power, exp=3)
32print(square(5)) # → 25
33print(cube(2)) # → 8
34
35# Key functions in functools
36from functools import wraps, lru_cache, singledispatch
37
38# lru_cache — memoization
39@lru_cache(maxsize=128)
40def 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
47def serialize(obj):
48 raise NotImplementedError
49
50@serialize.register(int)
51def _(obj):
52 return str(obj)
53
54@serialize.register(list)
55def _(obj):
56 return ",".join(str(x) for x in obj)
Type Annotations
annotations.py
Python
1# Basic annotations
2def add(a: int, b: int) -> int:
3 return a + b
4
5# Union — multiple possible types
6from typing import Union
7def parse_int(value: Union[str, int]) -> int:
8 return int(value)
9
10# Optional — short for Union[T, None]
11from typing import Optional
12def 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)
18def process(items: list[int]) -> list[str]:
19 return [str(x) for x in items]
20
21# Callable
22from typing import Callable
23def run_callback(cb: Callable[[int, int], int]) -> int:
24 return cb(1, 2)
25
26# TypeVar — generics
27from typing import TypeVar
28T = TypeVar("T")
29
30def first(items: list[T]) -> T | None:
31 return items[0] if items else None
32
33# Python 3.12+ concise generics
34def 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