Reputation: 24084
updated 2010-06-15T09:45:00Z
:I'm wondering how to implement prototypal inheritance in Python. There would seem to be two different approaches to this problem: classes as instances, and instances as classes.
The second method would seem to be more flexible, in that it could be applied to existing objects of varied types, while the first would likely be more convenient for typical use cases.
The idea here is to use a metaclass to cause instantiations to actually be classes, rather than objects. This approach looks something like this:
class ClassAsInstance(type):
""" ClassAsInstance(type)\n
>>> c = ClassAsInstance()
>>> c.prop = 6
It's sort of annoying to have to make everything a class method.
>>> c.jef = classmethod(lambda self: self.prop)
>>> c.jef()
6
>>> cc = c()
>>> cc.jef()
6
But it works.
>>> c.prop = 10
>>> cc.jef()
10
>>> c.jef = classmethod(lambda self: self.prop * 2)
>>> cc.jef()
20
"""
def __new__(self):
return type(self.__name__ + " descendant", (self, ), {})
I haven't really tested any complicated stuff with this approach, so it may have limitations.
With this approach, the idea is to use the type
constructor to create classes from objects. This is exemplified in Alex Martelli's answer, although the example he uses for this approach implements copy prototyping rather than allowing the descendants to inherit later changes to their prototypes.
My approach was to do something like this:
def createDescendant(obj):
return type(obj.__class__.__name__ + " descendant", (obj.__class__, ), obj.__dict__)()
which will work in sort of a javascript-y kind of way: changes to a given object will not influence its descendants, but changes to the parent object's __class__
(like a javascript prototype
) will. I gather that this is because the type
constructor copies obj.__dict__
rather than referencing it in some sort of mro-ish scheme.
I attempted to implement an improved version that would allow true prototypal inheritance, wherein objects would inherit updates to the parent objects. The idea was to assign the prototype object's __dict__
property to the same property of the newly-created class, the one that becomes the class of the descendant object.
However, this didn't work out, as I discovered that the __dict__
of a type
cannot be assigned to; this limitation also applies to classes derived from type
. I'm still curious if it's possible to get around this problem by creating an object that "implements the type protocol", as is done with iterables, sequences, etc., but does not actually inherit from type
. This might create other problems, such as those inherent to the delegational approach that Alex mentions in the first part of his answer.
Alex also suggests a third approach, that of delegation, wherein the state of an object is propagated to descendant objects via the __getattr__
magic method. Again, see Alex's answer for an example, as well as details on the limitations of this approach.
Further insights on the practicality of these approaches, as well as alternative suggestions, are hereby requested.
Upvotes: 3
Views: 2062
Reputation: 8203
This is a more robust version of the delegation approach. The main improvements are
when the inherited member is a method, then a method with the same underlying function, but bound to the original calling object is returned. This addresse the problem that @AlexMartelli raises in his answer:
...as long as you're also inheriting the state of the prototype, not just the behavior. Unfortunately, non-overridden methods from the prototype will not perceive any state that may have been overridden in the current object.
Cooperative inheritance conventions are followed so as not to break class based inheritance
One restriction is that the Proto
class must come first in the method resolution order for initialization to work correctly
import types
import inspect
class Proto(object):
def __new__(self, proto, *args, **kw):
return super(Proto, self).__new__(self, *args, **kw)
def __init__(self, proto, *args, **kw):
self.proto = proto
super(Proto, self).__init__(*args, **kw)
def __getattr__(self, name):
try:
attr = getattr(self.proto, name)
if (inspect.ismethod(attr) and attr.__self__ != None):
attr = types.MethodType(attr.__func__, self)
return attr
except AttributeError:
return super(Proto, self).__getattr__(name)
The call to b.getY()
below illustrates Alex's point and would fail if the FromPrototype
class in his answer was used instead of Proto
from delegate import Proto
class A(Proto):
x = "This is X"
def getY(self):
return self._y
class B(Proto):
_y = "This is Y"
class C(object):
def __getattr__(self, name):
return "So you want "+name
class D(B,C):
pass
if __name__ == "__main__":
a = A(None)
b = B(a)
print b.x
print b.getY()
d = D(a)
print d.x
print d.getY()
print d.z
Upvotes: 0
Reputation: 882481
If you need future alterations of the prototype object to be transparently reflected in all "descendants", then you must have recourse to explicit delegation. On normal methods, that's easily done via __getattr__
, e.g., deriving from:
class FromPrototype(object):
def __init__(self, proto):
self._proto = proto
def __getattr__(self, name):
return getattr(self._proto, name)
...as long as you're also inheriting the state of the prototype, not just the behavior. Unfortunately, non-overridden methods from the prototype will not perceive any state that may have been overridden in the current object. In addition, special methods (ones with magic names starting and ending in double underscore), which are looked up in the class rather than the instance, cannot be simply delegated this way. So, to fix these issues may require a lot of work.
If you're not concerned with "seamlessly inheriting" future alterations to the prototype, but are fine with taking a snapshot of the latter at "prototype inheritance" time, it's simpler:
import copy
def proto_inherit(proto):
obj = copy.deepcopy(proto)
obj.__class__ = type(obj.__class__.__name__, (obj.__class__,), {})
return obj
each object built this way has its own class so you can set special methods on the class (after getting the object from proto_inherit
) without affecting any other object (for normal methods, you can set them either on the class or on the instance, though always using the class would be more regular and consistent).
Upvotes: 4