Classes are state containers and namespaces, not the primary abstraction. This is a design choice. Two patterns:
- State machines: a few methods that mutate the receiver.
- Namespaces: a bundle of related functions and constants.
Supported:
- Single and multiple inheritance (C3 MRO) with
super(). @property/@x.setter.@staticmethodand@classmethod.- A curated dunder protocol for operators, indexing, iteration, hashing, context managers, and attribute fallback (see Operator overloading and protocols).
Out of scope: descriptors, metaclasses, __slots__.
State-machine pattern
3Namespace pattern
A class with no __init__ and no per-instance state is a namespace. Methods called on the class are unbound, with no self prepended.
0
3.14159
25
27Inheritance and super()
Single or multiple bases (class Sub(Base):, class C(A, B):). Methods not on the subclass resolve along the C3 linearization (the MRO). An inconsistent hierarchy raises TypeError at class creation. isinstance(x, Base) walks the ancestor chain, so Sub instances are also instances of every ancestor.
super() (zero-arg) delegates to the next class up the chain, bound to the current self. Most common in __init__ to extend a base constructor.
Rex (lab)
TrueB
inconsistent hierarchyAttribute access on classes vs instances
| Access form | Resolves to |
|---|---|
MyClass.attr | class member, returned as-is (no binding) |
MyClass.method() | method called directly, no self |
instance.attr | instance __dict__ first, then class |
instance.method() | bound method, self prepended |
setattr / delattr work on instances and on class objects. The latter mutates the class’s members.
Class decorators
A class decorator is called with the class object and its return value binds to the name. It can add or replace class attributes (cls.kind = ...) or return a replacement.
tagged
7Properties
@property turns a method into a read-only attribute. @x.setter makes it writable. Properties live on the class. Subclasses inherit and can override either side.
20
68.0
212.0The two-argument form property(fget, fset) also works without decorator syntax.
Static methods
@staticmethod makes a method that receives no implicit self. It is a plain function that lives in the class namespace, callable as Class.method(...) or instance.method(...) with identical arguments. Subclasses inherit it and can override it. Use it for helpers that belong to a class conceptually but need no receiver.
5
20.0The functional form staticmethod(func) also works without decorator syntax.
Class methods
@classmethod binds the class, not the instance, as the first argument. Accessed through a subclass, cls is the subclass, so alternate constructors return the right type down the hierarchy.
1 2
Color BrightThe functional form classmethod(func) also works without decorator syntax.
Operator overloading and protocols
Dunders (__add__, __eq__, __getitem__, …) plug a class into language protocols. Define them in the class body. The VM calls them when the matching operator, builtin, or syntax form runs.
7
TrueDunders are looked up on the class chain. The instance dict is skipped, so assigning obj.__add__ = ... has no effect. Subclasses inherit and may override.
Arithmetic
| Operator | Forward | Reflected |
|---|---|---|
a + b | __add__ | __radd__ |
a - b | __sub__ | __rsub__ |
a * b | __mul__ | __rmul__ |
a / b | __truediv__ | __rtruediv__ |
a // b | __floordiv__ | __rfloordiv__ |
a % b | __mod__ | __rmod__ |
a ** b | __pow__ | __rpow__ |
-a | __neg__ | - |
+a | __pos__ | - |
Return NotImplemented from the forward op to make the VM try the reflected op on the other operand. If both return NotImplemented (or neither is defined), the operation raises TypeError.
Subclass-first ordering applies here. When type(b) is a strict subclass of type(a), b.__radd__ runs before a.__add__. This lets a subclass override an inherited reflected op without touching the base.
sub.__radd__15
10Bitwise and shifts
The bitwise and shift operators follow the same forward/reflected protocol.
| Operator | Forward | Reflected |
|---|---|---|
a | b | __or__ | __ror__ |
a & b | __and__ | __rand__ |
a ^ b | __xor__ | __rxor__ |
a << b | __lshift__ | __rlshift__ |
a >> b | __rshift__ | __rrshift__ |
~a | __invert__ | - |
Comparison
| Operator | Forward | Reflected |
|---|---|---|
a == b | __eq__ | __eq__ |
a != b | __ne__ | __ne__ |
a < b | __lt__ | __gt__ |
a <= b | __le__ | __ge__ |
a > b | __gt__ | __lt__ |
a >= b | __ge__ | __le__ |
!= falls back to not __eq__ (coerced to bool) when __ne__ is absent. Every other comparison returns the dunder’s raw result. A __lt__ that returns 'A.lt' yields the string, not True.
True
FalseTruth and length
bool(x) (and any boolean context) consults, in order:
__bool__if defined. It must returnbool, elseTypeError.__len__if defined.Falsewhen the length is 0, elseTrue.- Default
True.
len(x) calls __len__ directly. It must return a non-negative int.
False
False True
5Indexing and containment
| Form | Dunder | Arguments |
|---|---|---|
obj[i] | __getitem__ | (self, i) |
obj[i] = v | __setitem__ | (self, i, value) |
del obj[i] | __delitem__ | (self, i) |
v in obj | __contains__ | (self, value) |
Slices pass as a slice object, so obj[1:3] calls __getitem__(self, slice(1, 3, None)). Indexes on built-in sequences coerce via __index__, including slice bounds. Dict keys never coerce.
Without __contains__, v in obj falls back to iterating obj and comparing with __eq__.
1 missing
True FalseIteration
| Method | Role |
|---|---|
__iter__ | Returns an iterator (often self). |
__next__ | Returns the next item, or raises StopIteration to end the loop. |
[1, 2, 3]
Truefor loops, list(x), and tuple(x) all honour the protocol.
Callable
__call__ makes instances invocable. Positional and keyword arguments are forwarded like any method call.
14
21
TrueHashing
hash(x) calls __hash__. It must return an int, which is masked to INT_MAX.
Eq/hash invariant. A class that defines __eq__ without __hash__ is unhashable. hash(x) and {x: 1} raise TypeError. This prevents inconsistent dict keys.
5
foundBuilt-in dict and set compare instance keys by identity. A user __hash__ is returned by hash(), but does not change containment in built-in containers. Use the same instance reference to look up reliably.
Representation
| Function / form | Dunder | Fallback |
|---|---|---|
repr(x) | __repr__ | <ClassName instance> |
str(x), print(x) | __str__ | __repr__, then default |
f"{x}" (no spec) | __str__ | same as str(x) |
f"{x:spec}" | __format__ | built-in format spec engine |
f"{x!r}" | __repr__ | - |
__format__(spec) receives the spec string and must return str. int(x) on an instance calls __int__, which is also used by %d / %x / %X / %o formatting. float(x) calls __float__ and falls back to __index__. abs(x) calls __abs__.
P(3)
P(3)
P(3)
[P(3)]Attribute access fallback
__getattr__(self, name) runs only when normal lookup (instance dict, then class chain) misses. It receives the name as a string. Return the value, or raise AttributeError to surface a real miss.
1
computed:anything
computed:fooExisting attributes bypass __getattr__. Only misses trigger it.
Context managers
with cm() as x: invokes __enter__. Its return value binds to as. On exit, __exit__(exc_type, exc_value, traceback) runs. The arguments are (None, None, None) on normal exit. On a raise, they carry the exception type and value, with traceback always None. A truthy return suppresses the exception. A falsy one propagates it.
afterMultiple managers (with a(), b() as x:) nest LIFO. b enters last and exits first. Each has its own implicit handler, so inner suppression still lets outer managers run their normal __exit__(None, None, None).
If __exit__ itself raises, the new exception replaces the original.
What’s not dispatched
Parsed for compatibility but never invoked on user classes:
__init_subclass__,__set_name__, descriptors (__get__/__set__/__delete__)__new__. The VM constructs the instance and__init__runs user logic.- Augmented-assignment dunders (
__iadd__, …).a += bdesugars toa = a + b, so__add__covers it. Exception: list+=extends in place (alias-visible). See Data types. - Async dunders (
__aenter__/__aexit__/__aiter__/__anext__).async withandasync foruse the sync paths. See Async.
What classes do not support
- Metaclasses, descriptors (
__get__/__set__),__slots__, ABCs,__init_subclass__. - Async dunders, covered above.
Reuse behaviour through free functions and composition by default. Reach for inheritance and operator overloading when the abstraction genuinely calls for them.