RUNTIME VALUE CONVERSIONS

Mutable classes and methods

Convert classes with declared fields, ordinary instance methods, and single or multiple inheritance. Runtime value conversion preserves shared instance identity and follows Python's C3 method resolution order.

Declare fields and methods

class Counter:
    name: str
    value: int

    def __init__(self, name: str, value: int) -> None:
        self.name = name
        self.value = value

    def add(self, amount: int) -> int:
        self.value += amount
        return self.value

class NonNegativeMixin:
    def add(self, amount: int) -> int:
        return super().add(max(0, amount))

class NonNegativeCounter(NonNegativeMixin, Counter):
    pass

def advance(counter: Counter, amount: int) -> Counter:
    counter.add(amount)
    return counter

def main() -> int:
    counter = NonNegativeCounter("jobs", 2)
    same = advance(counter, 3)
    add_more = same.add
    print(add_more(1), counter.value, counter is same)
    return 0

The program prints 6 6 True. The subclass inherits its fields and constructor from Counter. Its lookup order is NonNegativeCounter, NonNegativeMixin, Counter, object. The mixin's super().add reaches Counter.add and ignores negative amounts. The parameter, result, and local aliases refer to the same instance; the stored bound method retains its receiver.

Field declarations and writes

Declare each field in the class body as name: type, without a class-level initializer. Assign instance values through self.name in __init__, and initialize every field before it is read. Fields can hold supported scalar, union, list, tuple, dictionary, object, or declared-class values.

Field assignment checks the declared type. An assignment such as counter.value = "three" fails the integer field check. Reading an uninitialized field, creating an undeclared attribute, or accessing an unknown method reports a runtime error.

A subclass inherits fields from all its bases. Each field name has one instance slot, including in a diamond hierarchy. The first class in C3 order that declares the field supplies its annotation; an inherited copy does not hide a later class's own declaration. Redeclaring a field in the subclass takes precedence. This annotation checks all writes, including those made by inherited methods. These assignment checks are part of PyCForge's conversion contract; Python itself does not enforce field annotations. A field and method cannot share a name anywhere in the hierarchy. An annotation naming a class accepts its subclasses through any base, including inside supported container annotations.

A container field checks its current elements when the field is assigned. Mutating the container itself is separate from replacing the field; container annotations do not permanently constrain writes through aliases.

Calls and bound methods

A bound method receives its instance as the first argument, conventionally named self. Remaining parameters support defaults, keyword calls, positional-only and keyword-only markers, and variadic parameters under the function binding rules. Methods can read or update fields, call other supported functions, and return admitted values. Store or pass a bound method like a function value; Counter.add(counter, 1) calls the unbound function through its class.

Ordinary methods and __init__ accept function decorators. A function returned by a decorator binds to the instance; a variadic wrapper receives that instance in its first positional value. An already bound method retains its receiver, and other replacement values are returned unchanged. Decorators and defaults may use earlier methods in the class body; method bodies retain their module scope. The final constructor callable must return None.

Bound methods compare equal when they refer to the same instance and the same underlying method. Different receivers or methods remain distinct. Instances can be passed to functions, returned, and stored in supported containers.

Method order and super

Method lookup uses Python's C3 order, respecting the order of direct bases and each base's own ordering. The first class that declares the requested method wins, even when an inherited method makes the call. Duplicate bases and inconsistent orders are rejected during conversion. A class without its own __init__ inherits the first constructor in that order.

super() uses the method's declaring class and current first positional argument. Lookup continues after that declaring class in the actual receiver's C3 order, so it can reach a sibling base. It keeps the declaring class when a further subclass inherits the method. super(Class, instance) starts after the named class in the same order; a subclass object can be supplied instead of an instance to obtain unbound methods. Super proxies and their bound methods may be stored and passed to functions. Lookup reads methods declared on classes, not instance fields. Zero-argument calls need a valid lexical __class__ cell and first argument; nested functions use their own first argument. Inlined comprehensions follow Python 3.12 scope behavior.

isinstance(value, Class) and issubclass(Child, Base) follow the declared hierarchy. Class information also accepts nested tuples and the supported scalar, container, and range types; bool is a subtype of int. These calls do not accept union objects or custom instance/subclass protocols.

Cooperative constructors

Each constructor consumes its own arguments and forwards the remaining keywords. This separate example shares one root through two branches:

class Root:
    trace: list[str]

    def __init__(self) -> None:
        super().__init__()
        self.trace = ["root"]

class Left(Root):
    def __init__(self, *, left: int, **remaining) -> None:
        super().__init__(**remaining)
        self.trace.append(f"left={left}")

class Right(Root):
    def __init__(self, *, right: int, **remaining) -> None:
        super().__init__(**remaining)
        self.trace.append(f"right={right}")

class Combined(Left, Right):
    pass

def main() -> int:
    item = Combined(left=1, right=2)
    print(item.trace)
    return 0

Expected output: ['root', 'right=2', 'left=1']. Lookup follows Combined, Left, Right, Root, object. The root initializes the shared field once; the other constructors append as the calls return. The final object.__init__ receives no arguments.

Class forms and limits

For immutable records with direct typed conversion, use Simple immutable records.