Classes
py4 classes are value-based records with methods. They are closer to C structs with associated functions than to Python OOP.
Basic class
class Point: x: int y: int
def sum(self: Point) -> int: return self.x + self.y
def main() -> None: p: Point = Point(3, 4) p.x = 10 print(p.x) print(p.sum()) print(p)Instantiate with positional arguments matching the field order. The self parameter is always explicitly typed with the class name.
Methods
class Segment: start: Point end: Point
def span(self: Segment) -> int: return (self.end.x - self.start.x) + (self.end.y - self.start.y)
def main() -> None: s = Segment(Point(1, 2), Point(5, 9)) print(s.span())Methods lower to plain C functions. Dispatch is static — there is no vtable.
__init__
When you need custom initialization logic, define __init__. The self parameter is typed here too:
class Counter: value: int label: str
def __init__(self: Counter, start: int, name: str) -> None: self.value = start self.label = name
def bump(self: Counter, delta: int) -> int: return self.value + delta
def main() -> None: c: Counter = Counter(4, "hits") print(c.value) print(c.label) print(c.bump(3)) print(c)__init__ cannot be called directly as a method. It lowers to a C initializer called by the generated constructor.
Inheritance
Single inheritance is supported:
class Animal: name: str age: int
def label(self: Animal) -> str: return self.name
class Dog(Animal): bark: int
def total(self: Dog) -> int: return self.age + self.bark
class LoudDog(Dog): def twice(self: LoudDog) -> int: return self.total() + self.total()
def main() -> None: dog = Dog("rex", 4, 3) loud = LoudDog("max", 2, 5) print(dog.name) print(dog.label()) print(dog.total()) print(loud.label()) print(loud.twice()) print(loud)- Inherited fields are flattened into the subclass layout.
- Inherited methods are accessible on the subclass.
super()
super() is supported for parent method calls and parent __init__ calls within subclass methods:
class Animal: name: str
def __init__(self: Animal, name: str) -> None: self.name = name
def label(self: Animal) -> str: return self.name
class Dog(Animal): bark: int
def __init__(self: Dog, name: str, bark: int) -> None: super().__init__(name) self.bark = bark
def label(self: Dog) -> str: return super().label()
def total(self: Dog) -> int: return self.bark + len(super().label())
def main() -> None: dog = Dog("rex", 4) print(dog.label()) print(dog.total()) print(dog)super() is valid only inside subclass methods and only for method-call syntax.
Private members
Fields and methods prefixed with __ are private to the class:
class Counter: __value: int label: str
def __bump(self: Counter) -> int: self.__value = self.__value + 1 return self.__value
def reveal(self: Counter) -> int: return self.__bump()
def main() -> None: c: Counter = Counter(4, "hits") print(c.reveal()) print(c.label)External access to __value or __bump is rejected at compile time.
Value semantics
Classes are value types. Assignment copies the struct. Managed fields (lists, dicts) inside a class share underlying refcounted storage:
class Bucket: values: list[int]
def main() -> None: original: Bucket = Bucket([1, 2]) alias: Bucket = original alias.values.append(3) # shared list — both see the change print(original.values) # [1, 2, 3]
alias.values = [9] # reassigning breaks the alias print(original.values) # [1, 2, 3] print(alias.values) # [9]Limitations
- No dynamic attributes
- No
__str__,__repr__, or operator overloading - No multiple inheritance or MRO
superfield access is not supported, only method calls- Method dispatch is always static