Hi all,
I’d like to start a discussion around introducing Design by Contract (DbC) into Python, specifically through native support for class invariants.
What I’m proposing
The idea is to allow classes to define an __invariant__()
method that is automatically checked:
- Before and after every public method call
- Without needing decorators, metaclasses, or manual calls
- Ideally in a way that’s opt-in to avoid runtime overhead for all classes
This would make it possible to build self-verifying classes that enforce consistency at runtime—especially useful in stateful systems, simulations, or anywhere where correctness matters.
Why this matters
Python provides flexible tools for enforcing invariants, but it lacks a built-in, standardized mechanism for ensuring object consistency by construction—such as automatic validation of class invariants before and after method execution. Class invariants are a common concept in formal methods and have been used successfully in languages like Eiffel, D, Ada, and even early .NET experiments with Code Contracts (and Spec#).
Today in Python, trying to enforce invariants is awkward:
- You have to call your invariant method manually inside every method.
- Or use fragile tricks like setattr, decorators, or metaclasses.
- These approaches are verbose, error-prone, and often slow.
A built-in mechanism would allow consistent, clean enforcement of class-level contracts in a Pythonic way.
Possible syntax / semantics
This could work like:
class Account:
__dbc__ = True # Opt-in to DbC
def __init__(self):
self.balance = 0
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
self.balance -= amount
def __invariant__(self):
assert self.balance >= 0, "Balance must never be negative"
When enabled, Python would automatically call __invariant__()
before and after any public method.
Alternatives:
- Use a class decorator like @contract
- Only enforce in debug mode / debug
- Let users enable it per-class or globally
Implementation status
I attempted to build this as a C extension (Invariant-Python) that hooks into CPython internals (e.g., PyObject_Call). Unfortunately, Python’s object model and dynamic dispatch made this more complex than expected, and I hit a wall. So I came up with a rather basic implementation: Invariant Python purely to illustrate the idea.
What I’d love feedback on
- Would this idea make sense in the Python language itself?
- Are there internal hooks that could make this feasible without major overhead?
- Any interest in helping prototype it or shaping a possible PEP?
Thanks for reading! Happy to elaborate further or adjust direction based on your input.
— Andrea