views:

64

answers:

1

updated 2010-06-15T09:45:00Z:

  • added an example for the "classes as instances" approach, and an explanation of the "instances as classes" approach; incorporated and referenced Alex Martelli's answer;

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.

classes as instances

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.

instances as classes

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.

delegation

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.

+2  A: 

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).

Alex Martelli
Interesting. I worked your answer into a new version of the question, and also added some further explanation of my original take on the problem.
intuited