RUNTIME VALUE CONVERSIONS
Pass supported values and callables between functions, declare the types that your boundaries accept, and capture local variables with nested functions or lambdas.
Runtime functions accept positional and keyword arguments, defaults, positional-only parameters before /, keyword-only parameters, and variadic parameters. *args collects extra positional values into a tuple; **kwargs collects extra keywords into an ordered dictionary. Parameter and return annotations are optional. Returning a mutable value preserves its shared identity.
The same binding rules apply to top-level and nested functions, lambdas, constructors, and bound methods. Missing required arguments, duplicate values, and unexpected keywords report errors. A positional-only name may also appear as an independent key in **kwargs. Typed conversion has separate required keyword-call forms.
Calls accept *iterable and **dictionary, including multiple expansions mixed with ordinary arguments. Dictionary keys must be strings without embedded NUL characters; duplicate keywords report an error. The callee is evaluated first, followed by positional expressions and then keyword expressions. A sole *iterable remains live until keyword evaluation finishes; mixed positional expansions are consumed in sequence.
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 0This prints jobs: 3. and items: 1!. The name is positional-only; the suffix is keyword-only.
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 or dictionary default is therefore shared; use a None default and create a list inside the function when each call needs its own list.
Each execution of a nested definition creates a new function with its own defaults. Captured variables remain shared with that function's enclosing scope.
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 0This 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.
def remember(value: int, history: list[int] | None = None) -> int:
if history is None:
history = []
history.append(value)
return len(history)
def main() -> int:
print(remember(4), remember(7))
return 0
This prints 1 1. Each omitted history creates a separate list. Passing an existing list deliberately still shares that list.
| Annotation | Accepted value |
|---|---|
bool, int, float, str, None | The corresponding runtime kind. Integer checks also admit Boolean values; float checks require floating values. |
| A declared class name | An instance of that class or any of its declared subclasses. |
int | str, Counter | None | A value accepted by either annotation. |
list, tuple, dict, set, frozenset | The named container kind without an element constraint. |
dict[K, V] | A dictionary whose current keys satisfy K and values satisfy V. |
list[T], set[T], frozenset[T] | The named container kind 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. |
object | Any 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
choose(True) returns 4; choose(False) returns "waiting".
|
PyCForge caveats Annotations become runtime checks. Ordinary Python does not enforce these hints, but generated Runtime C checks annotated arguments, returns, local assignments, and field writes. Use a union such as |
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.
An annotation on *args checks each extra argument; an annotation on **kwargs checks each extra keyword value. Stores through nonlocal retain the enclosing binding's annotation checks.
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.
def increment_result(operation):
def wrapper(value: int) -> int:
return operation(value) + 1
return wrapper
@increment_result
def calculate(value: int) -> int:
return value
calculate(4) returns 5. The decorator receives the original function and returns its replacement. Functions, lambdas, and bound methods can also be passed directly as arguments, returned, or stored in containers.
Top-level functions, nested functions, and ordinary methods accept decorators and decorator factories. Decorator expressions run from top to bottom before defaults are evaluated; their results are applied from bottom to top. The name is bound only after all applications finish. Wrappers can forward *args and **kwargs and retain shared state. Each wrapped function keeps its own argument and annotation checks.
Decorators use the same supported operations as other functions, including explicitly supplied modules. Class decorators, static/class methods, properties, function metadata attributes, and implicit standard-library imports such as functools are unsupported.
def make_offset(start: int):
offset = start
def transform(value):
nonlocal offset
offset += 1
return value + offset
return transform
def calculate(value: int) -> int:
transform = make_offset(3)
return transform(value)
calculate(10) returns 14. The nested function changes the enclosing offset through nonlocal. Without that declaration, assigning the same name creates a separate local binding. Captured storage survives the creating function and remains valid for the C context's lifetime.
Nested functions support recursion and calls between sibling functions once their definitions have executed. Lambdas use the same parameter forms with an expression body. Both can be returned, passed to other functions, or stored in containers; distinct creations have distinct identity. Deleting a local or nonlocal name makes it uninitialized until assigned again. Nested classes and mutable module-global bindings are unsupported.
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 0This prints 10 12. A captured mutable object is still shared; a default saves its reference, not a deep copy.
Functions have at most 64 decorators and 64 declared parameters, including variadic names; calls accept at most 64 supplied values after expansion. Functions have at most 1,024 local bindings; a closure can capture up to 256 local cells. Calls are checked against a depth limit of 128. Invalid arguments, incompatible annotations, uninitialized reads, and resource exhaustion report runtime errors. See C caller error handling.