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
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
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).
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}
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:
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.
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.
| 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.