RUNTIME VALUE CONVERSIONS

Functions, annotations, and lambdas

Pass supported values and callables between functions, declare the types that your boundaries accept, and use lambdas to capture local variables.

Function parameters and returns

Runtime functions accept positional and keyword arguments, default values, positional-only parameters before /, and keyword-only parameters after *. Parameter and return annotations are optional. A function can return a string, container, instance, or callable without copying a shared mutable object. All supplied arguments are evaluated once, from left to right, after the callee.

The same binding rules apply to top-level functions, lambdas, constructors, and bound methods. Missing required arguments, duplicate values, unknown keyword names, or a positional-only parameter supplied by keyword report an error. Variadic *args, **kwargs, and argument expansion are unsupported. Typed conversion has separate required keyword-call forms.

def label(name: str, /, count: int = 1, *, suffix: str = "!") -> str:
    return f"{name.strip()}: {count}{suffix}"

def main() -> int:
    print(label(" jobs ", suffix=".", count=3))
    print(label("items"))
    return 0

This prints jobs: 3. and items: 1!. The name is positional-only; the suffix is keyword-only.

When defaults are created

Default expressions are evaluated once, from left to right, when each function or lambda is defined. Repeated calls reuse the same stored value. A mutable list default is therefore shared; use a None default and create a list inside the function when each call needs its own list.

def remember(value: int, history: list[int] = []) -> int:
    history.append(value)
    return len(history)

def main() -> int:
    print(remember(4), remember(value=7), remember(9, history=[]))
    return 0

This prints 1 2 1. Module function and method defaults are initialized in source order once per C context, before its first public function call. They may use supported expressions and earlier module definitions; referencing a later definition at that point reports a name error.

Supported annotations

AnnotationAccepted value
bool, int, float, str, NoneThe corresponding runtime kind. Integer checks also admit Boolean values; float checks require floating values.
A declared class nameAn instance of that class.
int | str, Counter | NoneA value accepted by either annotation.
list, tupleThe named container kind without an element constraint.
list[T]A list whose current elements satisfy T.
tuple[T, U]A fixed-length tuple with separately checked positions.
tuple[T, ...]A tuple of any admitted length whose elements satisfy T.
objectAny initialized supported value, including a callable.

Use annotation names directly. Imports from typing, quoted forward annotations, and general annotation expressions are unsupported.

def choose(ready: bool) -> int | str:
    result: int | str
    if ready:
        result = 4
    else:
        result = "waiting"
    return result

Function and assignment checks

Annotations check arguments on entry, returned values, stores to annotated local names, and stores to declared fields. An annotation without an initial value leaves the local uninitialized. Every executed read still needs an earlier assignment.

Checks do not coerce values: use 1.0 or float(value) when a float is required. An unannotated binding may change type; an annotated binding must continue to satisfy its declared annotation. Redeclaring a name with an incompatible annotation is rejected.

Container checks inspect elements at the boundary being crossed. They do not permanently bind a type schema to every alias. For example, appending text through an unannotated alias of a list does not itself enforce an earlier list[int] declaration; passing that list to an annotated list[int] parameter then fails. This checking is part of the C conversion contract; Python itself does not enforce ordinary annotations at runtime.

Pass a function value

def apply(operation, value: int) -> int:
    return operation(value)

def increment(value: int) -> int:
    return value + 1

def calculate(value: int) -> int:
    return apply(increment, value)

calculate(4) returns 5. Lambdas and bound methods may be passed in the same way.

Lambdas and captured variables

def make_offset(start: int):
    offset = start
    transform = lambda value: value + offset
    offset += 1
    return transform

def calculate(value: int) -> int:
    transform = make_offset(3)
    return transform(value)

calculate(10) returns 14. The lambda captures the live local variable, so it observes the changed offset. Captured storage survives the creating function and remains valid for the C context's lifetime.

Lambdas use the same positional, keyword, default, and keyword-only parameter forms as functions, with a supported expression body. They can be nested, returned, passed to other functions, or stored in containers. Distinct lambda creations have distinct identity.

Late binding in a comprehension

def main() -> int:
    operations = [lambda value: value + offset for offset in range(3)]
    print(operations[0](10), operations[2](10))
    return 0

Both calls print 12: the lambdas share the comprehension variable, whose final value is 2. A default parameter captures the current value when each lambda is created:

def main() -> int:
    operations = [lambda value, offset=offset: value + offset
                  for offset in range(3)]
    print(operations[0](10), operations[2](value=10))
    return 0

This prints 10 12. A captured mutable object is still shared; a default saves its reference, not a deep copy.

Call and capture limits

Functions have at most 64 parameters and calls accept at most 64 explicit arguments. Functions have at most 1,024 local bindings; a lambda can capture up to 256 local cells. Calls are checked against a depth limit of 128. Invalid argument counts, incompatible annotations, uninitialized reads, and resource exhaustion report runtime errors. See C caller error handling.