Skip to content

Class members

Closed member kinds

Class bodies contain a closed set of member kinds:

Member kind Example
instance field x: int
method def f(self): ...
class method classmethod f(cls): ...
factory factory f(cls): ...
getter getter x(self) -> T: ...
setter setter x(self, value: T): ...
class member variable classvar count: int = 0

The set stays closed because descriptors are gone: Python's __get__/__set__/__delete__ protocol lets a class invent an eighth kind of member with its own attribute-access behavior, and Lucid has no such hook for one to plug into.

Read-only methods with ~Self

Self is a builtin type, referring to the enclosing class. A method's self parameter is Self by default — the ordinary mutable type — but a method that only reads its object, never writing to it, should annotate it self: ~Self, the read-only view (see Mutable, read-only, and immutable views):

class Counter:
    value: int

    def get(self: ~Self) -> int:
        return self.value

    def increment(self):
        self.value += 1
This is what lets a frozen object know which of its methods are safe to call. Since both Self and !Self are subtypes of ~Self, a method declared self: ~Self is callable on a mutable, read-only, or frozen receiver, while a method left at the default self: Self requires a mutable receiver and is not available on a read-only or frozen value at all:

counter: Counter = Counter(0)
frozen: !Counter = freeze(counter)

frozen.get()        # fine: get takes self: ~Self
frozen.increment()  # error: increment takes self: Self, frozen is not mutable
A getter is read-only by construction — computing a value from an object should never require write access — so it behaves as though self: ~Self were already implied.

Class member variables

Class member variables are marked with classvar. A plain annotated assignment in the class body declares an instance field; if it has a value, that value is the field's default — evaluated fresh per instance or shared across all of them depending on the field's own type (see Default values).

class User:
    classvar count: int = 0
    name: str
    active: bool = true
This separates shared class state from stored instance fields.

Field docstrings and metadata

A field's docstring and metadata are metadata blocks, standalone ; lines right after the field:

class Layer:
    weights: Array
    ; "the layer's learnable weight matrix"

    activation: str = "relu"
    ; "the nonlinearity applied after the affine transform"
    ; {"static": true}
No new keyword or builtin call is needed — a checked, structured replacement for what a leading string literal only conventionally means in Python. The fields() builtin reports both alongside each field's name and value.

Final fields

final marks a field that can be set once and never reassigned again, regardless of which view — T, ~T, or !T — the caller holds. It is a property of the binding, not the type: !T says the value on the other end of a reference cannot change; final says the reference itself cannot be pointed somewhere else. The two compose independently:

class Session:
    final id: str
    final model: InferenceModel
    final config: !InferenceModel
id and model cannot be rebound after construction, but model's own fields can still be mutated in place, since InferenceModel on its own is an ordinary mutable type. config cannot be rebound, and the object it points to cannot change either.

A final field is normally set once, in a factory. Assigning to it again anywhere afterward — from any method, through any view — is an error.

No static methods for namespaced functions

If a function does not need access to an object or class, it stays a function. Lucid does not use static methods as namespaced free functions.

Explicit class methods

Class methods receive cls and are inherited as class-level behavior.

class User:
    classvar count: int = 0

    classmethod total_created(cls) -> int:
        return cls.count

Value semantics options

Classes can request common value semantics with class options. Immutability is not a class option; it is represented by the !T view.

class Point(eq=true, order=true, hash=true):
    x: float
    y: float
Supported core options:

Option Meaning
eq equality is generated
order ordering methods are generated
hash hashing behavior is generated for immutable values

Representation is generated by default unless the class defines its own representation method.

Class inheritance covers what a class can extend, and what stays closed off from Python's own inheritance hooks.