A prototype-based delegation system for Python 3 that replaces class-based inheritance with object composition through delegation.
This library implements a SELF-inspired delegation model that eliminates the complexity of Python's class system while maintaining compatibility with existing Python code. By treating all objects uniformly, it removes the need for classmethods, staticmethods, and metaclasses, replacing these constructs with a single, consistent method lookup mechanism.
Traditional class-based inheritance introduces unnecessary complexity through special cases and exceptions. Prototype-based delegation offers a simpler model built on three principles:
- Uniform object model - All objects have equal status; no distinction between classes and instances
- Consistent lookup - Method resolution follows a single, predictable algorithm
- Uniform binding - All methods bind identically, without special decorators or descriptors
This approach is inspired by SELF: The Power of Simplicity, adapted to work within Python's ecosystem while maintaining interoperability with standard Python classes and objects.
This is a single-file library with minimal dependencies. Install the required testing framework:
pip install selftestThen import the module:
fromprototypeimportprototypeObjects are created directly without classes. Parents and attributes are specified at instantiation:
fromprototypeimportprototype# Base object with a methodbase=prototype(area=lambdaself: self.x*self.y)
# Objects with parents and attributesobj_x=prototype(base, x=3)
obj_y=prototype(base, y=4)
# Multiple inheritance through delegationcomposite=prototype(obj_x, obj_y)
print(composite.area()) # 12Method lookup uses C3 linearization (identical to Python's MRO) implemented via topological sort. The algorithm constructs a dependency graph from parent relationships and traverses it in deterministic order, ensuring consistent resolution in complex delegation hierarchies.
Methods receive up to three automatically injected parameters based on their signature:
self- The object on which the method was invoked (receiver)this- The object where the method was defined (definer)super- Proxy to parent objects for method refinement
Parameters are injected by introspecting function signatures. Declare only the parameters you need:
# Access to receiver onlyobj=prototype(get_x=lambdaself: self.x)
# Access to definer and receiverobj=prototype(identify=lambdaself, this: (self, this))
# Method refinement with superbase=prototype(compute=lambdan: n*3)
refined=prototype(base, compute=lambdasuper, n: 2*super.compute(n))
refined.compute(5) # 30The super parameter provides access to parent implementations, enabling method refinement without explicit parent references.
The system interoperates with standard Python classes and objects:
classPythonClass:
defmethod(self):
return42# Delegate to a classobj=prototype(PythonClass)
obj.method() # 42# Delegate to an instanceinstance=PythonClass()
obj=prototype(instance)
obj.method() # 42For compatibility, functions using cls as the first parameter receive this instead of self, matching Python's classmethod behavior.
Prototype objects can be defined using class syntax for familiarity:
classShape(prototype):
defarea(self):
returnself.x*self.yclassrectangle(Shape):
x=3y=4rectangle.area() # 12This syntax creates prototype objects, not classes. The metaclass intercepts class creation and returns prototype instances.
The library consists of three core components:
prototype- Main class representing all objects, stores parents in__bases__and attributes in__dict__method- Bound method wrapper that handles parameter injection and serves as thesuperproxymeta- Metaclass that enables class syntax by intercepting class definitions
deflinearize(obj):
"""C3 compatible linearization via topological sort"""# Build dependency graph from parent relationships# Traverse in static order# Return linearized list of objectsThe lookup() method traverses this linearization, checking each object's __dict__ for the requested attribute. When found, functions are wrapped in method objects that handle parameter injection.
Special methods (__call__, __eq__, __hash__, etc.) are looked up in the instance before falling back to the class, matching Python's behavior while maintaining delegation semantics.
classA(prototype):
x=0y=0defproduct(self):
returnself.x*self.yclassB(A):
x=5classC(A):
y=3classD(B, C):
passD.product() # 15# Base implementationlogger=prototype(
log=lambdamsg: print(f"[LOG] {msg}")
)
# Refined implementationtimestamped_logger=prototype(
logger,
log=lambdasuper, msg: super.log(f"{time.time()}: {msg}")
)
# Further refinementfiltered_logger=prototype(
timestamped_logger,
log=lambdaself, super, msg: super.log(msg) ifself.level>0elseNone,
level=1
)# Create objects at runtimedefmake_counter(start=0):
defincrement(self):
self.value+=1returnself.valuedefdecrement(self):
self.value-=1returnself.valuereturnprototype(
value=start,
increment=increment,
decrement=decrement
)
counter=make_counter(10)
counter.increment() # 11counter.decrement() # 10The library includes comprehensive inline tests using the @test decorator. Tests cover:
- Object creation and initialization
- Method lookup and binding
- Parameter injection
- Python class/object delegation
- C3 linearization
- Dunder method handling
- Edge cases and error conditions
Refer to prototype.py for complete test coverage and additional usage examples.
- Python 3.9+ (requires
graphlib.TopologicalSorter) selftest- Testing framework for inline tests
GNU General Public License v3.0
- SELF: The Power of Simplicity - Original inspiration
- Python C3 Linearization - Method resolution order
- Python graphlib - Topological sorting implementation