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.