Functions
A function packages a call contract and a body. Calling it binds arguments to parameters, executes the body, and returns a value or raises an exception.
def clamp(value: float, lower: float, upper: float, *, strict: bool = False) -> float:
if lower > upper:
raise ValueError("lower must not exceed upper")
if strict and not lower <= value <= upper:
raise ValueError("value is outside the interval")
return min(max(value, lower), upper)
The * makes following parameters keyword-only, which is useful for behavioral
flags whose meaning would be unclear positionally.
Parameter kinds
Python supports positional-only parameters before /, positional-or-keyword
parameters, variadic positional *args, keyword-only parameters after *, and
variadic keyword **kwargs. Use the narrowest interface that communicates
intent; forwarding arbitrary arguments weakens discoverability.
Defaults are evaluated once
Default expressions run when the function is defined, not on every call. Avoid shared mutable defaults:
def collect(item: str, bucket: list[str] | None = None) -> list[str]:
if bucket is None:
bucket = []
bucket.append(item)
return bucket
Return and ownership
Reaching the end without return returns None. Returning multiple comma-
separated values constructs a tuple. Document whether a function mutates an
argument, returns a new object, performs I/O, or retains references.
Annotations and docstrings
Annotations communicate intended types to readers and tools; the interpreter does not generally enforce them. A concise docstring should explain behavior, important invariants, raised exceptions, and surprising side effects rather than repeat the signature.
Design rules
- Separate pure calculation from I/O when practical.
- Prefer explicit result values over printing inside reusable logic.
- Raise specific exceptions at the boundary where invalid state is recognized.
- Keep a function at one useful level of abstraction.