Skip to content

Classes & objects

This page covers classes in d/Python: creating objects, fields and methods, inheriting from one parent class, super(), telling objects apart, the special methods (such as __str__ and __len__) that work, objects you can call like functions, and isinstance checks. Classes were part of d/Python's plan from the very start, not an add-on. Everything listed on this page works today; the compatibility table lists what does not yet.

A class, a subclass, super()

class Animal:
    kind = "animal"

    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

    def __str__(self):
        return f"{self.name} the {self.kind} says {self.speak()}"

class Dog(Animal):
    kind = "dog"

    def speak(self):
        return "Woof"

class Puppy(Dog):
    def __init__(self, name):
        super().__init__(name)
        self.age = 0

    def speak(self):
        return super().speak() + "!"

rex = Dog("Rex")
bit = Puppy("Bit")
print(rex)
print(bit)
print(isinstance(bit, Dog), isinstance(rex, Puppy), issubclass(Puppy, Animal), rex is rex, rex is bit)

This example uses class-level defaults (kind), instance fields set in __init__, a chain of single inheritance, super(), __str__, isinstance and issubclass on your own classes, and is to compare identity. Objects are shared by reference: put one in a list and the list holds the same object you do, not a copy. One limit: a list holds objects of one class only, so keep your Dogs and your Puppys in separate lists for now. Lists that mix classes are not yet supported.

Protocols that work

class Stack:
    def __init__(self):
        self.items = [0]
        self.items.clear()

    def push(self, value):
        self.items.append(value)

    def pop(self):
        return self.items.pop()

    def __len__(self):
        return len(self.items)

    def __bool__(self):
        return len(self.items) > 0

    def __repr__(self):
        return f"Stack({self.items!r})"

stack = Stack()
print(bool(stack), repr(stack))
stack.push(1)
stack.push(2)
print(len(stack), stack, stack.pop(), bool(stack))
Protocol Status
__init__, __str__, __repr__, __bool__, __len__ work; if a class leaves one out, Python's usual fallback is used, and a method that returns the wrong kind of value raises an error you can catch
__call__ works; an object can be called like a function and keep its own state, including as a sorted key or a map function
__iter__, __next__ work with every kind of loop and consumer that d/Python supports
__enter__, __exit__ work with the ordinary with statement (not async with)
__index__ works for bin, oct, hex and chr
__eq__, __lt__ and rich comparison, __getitem__, __contains__, __add__ and the numeric protocols not yet supported; comparing with is works, and using == on your own objects where the answer would depend on the class stops the compiler with a message
@property, @staticmethod, @classmethod, descriptors, __slots__, metaclasses, multiple inheritance not yet supported

Callable objects

class Counter:
    def __init__(self, start):
        self.value = start

    def __call__(self, amount=1, /, *, scale=1):
        self.value += amount * scale
        return self.value

counter = Counter(7)
saved = counter.__call__
print(counter(), counter(2, scale=3), saved(1), callable(counter))

Calling counter() looks up __call__ on the class, or on the closest parent class that defines it. Assigning a function to __call__ on a single object does not make that object callable, which matches CPython.

Bound methods

class Greeter:
    def __init__(self, greeting):
        self.greeting = greeting

    def greet(self, name):
        return self.greeting + ", " + name

hello = Greeter("Hello").greet
print(hello("Ada"), hello.__self__.greeting, hello.__func__ is Greeter.greet)

Taking a method from an object without calling it gives you a bound method, which remembers the object it came from. __self__ is that object and __func__ is the plain function defined in the class.

Classes as values

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

make = Point
origin = make(0, 0)
print(origin, isinstance(origin, make), Point.__name__)

A class is a value: you can give it another name, pass it around, import it from another file and use it as a parent class, and you can read its __name__ and __module__. Not yet supported: class information that only exists while the program runs, and using builtin types as values beyond the specific cases the compiler already handles.

Lifetime

The d/OS runtime (the program that runs your compiled code) counts the references to each object. A temporary object that no statement needs any more is freed when the statement finishes; an object stays alive while a variable, a class default or an exception being handled still points to it; and closing a window frees the objects that stood for it. There is no garbage-collector pause, and no promise that objects are finalized at the same moments CPython happens to finalize them. A run can have at most 256 objects alive at once today. Going over that limit stops the program with a clear message; it never changes what your program computes.

What the tests cover

Every claim on this page comes from a test program that runs in both d/Python and CPython and must print the same thing. The corpus page lists those programs, with their reviewed output.