on Sep 11th, 2008Prototype based programming in python

Revision #2:
I updated my code with some more bells and whistles, also removing the double-underscore-method/attributes and operator overloading as some people commented so “nicely” on.

prototype.py

class Object(object):
    def __init__(self):
        self._parent = None
        self._methods = {}

    def clone(self):
        o = Object()
        o._parent = self
        return o

    def _getmethod(self, name):
        try:
            return self._methods[name]
        except KeyError:
            return self._parent._getmethod(name)

    def __getattr__(self, name):
        method = self._getmethod(name)

        if isinstance(method, Method) and method.object is not self:
            self._methods[name] = self._methods[name] = Method(method.method, self)

        return self._methods[name]

class Method(object):
    def __init__(self, method, object = None):
        self.method = method
        self.object = object

    def __call__(self, *args, **kw):
        return self.method(self.object, *args, **kw)

def method(obj):
    def decorator(f):
        obj._methods[f.__name__] = Method(f, obj)
        return obj._methods[f.__name__]
    return decorator

def siblings(a, b):
    return a._parent is b._parent

def mixin(into, mixin):
    if isinstance(mixin, Object):
        for name, wrapper in mixin._methods.iteritems():
            into._methods[name] = wrapper

def child_of(child, parent):
    comp = child

    while comp is not None:
        if comp is parent:
            return True

        comp = comp._parent

    return False

def sibling(obj):
    return obj._parent.clone()

def is_prototype(obj):
    return typeof(obj) is Object

def clone():
    return Object()

and, demo.py

import prototype as p
from prototype import method

# Basic usage example

animal = p.clone()
animal.name = None

cat = animal.clone()
cat.name = "Cat"
cat.color = None

@method(cat)
def meow(self, times = 1):
   print ("%s says meow, and is of the color %s" % (self.name, self.color))*times

nermal = cat.clone()
nermal.name = "Nermal"
nermal.color = "grey"

garfield = p.sibling(nermal) # same as cat.clone() or nermal._parent.clone()
garfield.name = "Garfield"
garfield.color = "orange"

print cat.name
print garfield.name
print nermal.name

cat.meow()
garfield.meow()
nermal.meow()

kitten_1 = garfield.clone()
kitten_1.name = "Kiten #1"
kitten_1.color = "brown"

kitten_2 = p.sibling(kitten_1)
kitten_2.name = "Kitten #2"
kitten_2.color = "White"
garfield.name = "Garfield ..."
garfield.color = "Orange ..."
cat.name = "Original cat"
cat.color = "Still none"

cat.meow()
garfield.meow()
nermal.meow()
kitten_1.meow()
kitten_2.meow()

# Mixin example

mixin = p.clone()

@method(mixin)
def say_name_two_times(self):
    print self.name*2

@method(mixin)
def say_name_three_times(self):
    print self.name*5

p.mixin(garfield, mixin)
garfield.say_name_two_times()
garfield.say_name_three_times()

print p.child_of(garfield, cat)
print p.child_of(nermal, garfield)
print p.siblings(nermal, garfield)

, will print something like this:

Cat
Garfield
Nermal
Cat says meow, and is of the color None
Garfield says meow, and is of the color orange
Nermal says meow, and is of the color grey
Original cat says meow, and is of the color Still none
Garfield ... says meow, and is of the color Orange ...
Nermal says meow, and is of the color grey
Kiten #1 says meow, and is of the color brown
Kitten #2 says meow, and is of the color White
Garfield ...Garfield ...
Garfield ...Garfield ...Garfield ...Garfield ...Garfield ...
True
False
True


I love python.

Original version of the code
Revision #1:

class Object(object):
        def __init__(self):
                self.__parent__ = None
                self.__methods__ = {}

        def clone(self):
                o = Object()
                o.__parent__ = self
                return o

        def __pos__(self):
                return self.clone()

        def __getmethod__(self, name):
                try:
                        return self.__methods__[name]
                except KeyError:
                        return self.__parent__.__getmethod__(name)

        def __getattr__(self, name):
                method = self.__getmethod__(name)

                if isinstance(method, Method) and method.object is not self:
                        method = self.__methods__[name] = Method(method.method, self)

                return method

class Method(object):
        def __init__(self, method, object = None):
                self.method = method
                self.object = object

        def __call__(self, *args, **kw):
                return self.method(self.object, *args, **kw)

        def __isbound__(self):
                return object != None

def method(obj):
        def decorator(f):
                _method = Method(f, obj)
                obj.__methods__[f.__name__] = _method
                return _method

        return decorator

if __name__ == "__main__":
        animal = Object()
        cat = +animal
        cat.name = None

        @method(cat)
        def meow(self, times=1):
                print ("%s says meow " % self.name)*times

        cat.meow()

        fluffy = +cat
        fluffy.name = "fluffy"
        fluffy.meow()

        cat.name = "original cat"
        cat.meow()
        fluffy.meow()

        puffy = +fluffy
        puffy.name = "puffy"
        puffy.meow()
        fluffy.meow()
        cat.meow()

The above prints:

None says meow
fluffy says meow
original cat says meow
fluffy says meow
puffy says meow
fluffy says meow
original cat says meow

A very simple example on how to work with prototype-based programming in Python.

24 Responses to “Prototype based programming in python”

  1. Henrik Johanssonon 11 Sep 2008 at 6:21 pm

    Interesting. Clever use of the __pos__ method !

  2. N/Aon 11 Sep 2008 at 9:32 pm

    Go back to Ruby you filthy monkey coder. This code is as unpythonic as it gets.

    Did somebody ever tell you that __*__ methods are for Python and Python only to define? I can assure you, they *really* are.

    Also, a big slap across your face for:
    KEEP
    IT
    SIMPLE
    STUPID

    Just because the runtime happens to support a load of dynamism you don’t have a reason to make highly magic code. I know this is a proof of concept, but really, don’t use magic. Don’t. You’ll just end up with a specialized API that behaves like nothing else, and… Yeah, the difference between Ruby and Python code pretty much.

  3. anonymouson 11 Sep 2008 at 9:37 pm

    Please never use __double_leading_and_trailing_underscore__ for method and attribute names http://www.python.org/dev/peps/pep-0008/

  4. Mikael Janssonon 11 Sep 2008 at 9:43 pm

    Yawn. Get back when you’ve tried CLOS, or any other generic-function multiple-dispatch object system.

  5. Kzeonon 12 Sep 2008 at 1:25 am

    Very easy to make a such nice “python fanboi” comment while posting as a N/A user… duh…

    Anyway, thanks for the review of the code, it cleans obviously some flaws, quite welcome.

  6. Robertoon 12 Sep 2008 at 1:56 am

    Really clever!

  7. Sébastienon 12 Sep 2008 at 2:32 am

    Nice experiment, having a prototype based object model makes it much easier to “prototype” applications and sketch out solutions.

    I would be curious to know the additional cost of using prototypes in Python, particularily on method invocation.

  8. Sébastien Vincenton 12 Sep 2008 at 10:57 am

    I think there’s a problem in these two lines :

    if isinstance(method, Method) and method.object is not self:
    self._methods[name] = self._methods[name] = Method(method.method, self)

    self._methods[name] is affected twice.

  9. Alexander Artemenkoon 12 Sep 2008 at 11:11 am

    Why not try __metaclass__ keyword for same purpose?

  10. Fredrik Holmströmon 12 Sep 2008 at 11:51 am

    Sébastien Vincent: Oh yes, you’re correct - my bad, was a bit tired last nite when i revisited the code.

    Alexander: Clever idea, I have a feeling Revision #3 is comming later today ;)

  11. links for 2008-09-12 | NeXton 12 Sep 2008 at 3:35 pm

    […] Love and Theft » Blog Archive » Prototype based programming in python (tags: programming design Python prototype) […]

  12. michaelon 12 Sep 2008 at 4:23 pm

    Quite franly I think the notion to use _ as leading part of a method name is a wrong general idea. I can even adjust to CamelCase easier than to do def _foo definitions. It reminds me of global variables in perl, just that in this case they are tied to the method/function - both are ugly ;)

    From a conceptual point of view, I think a “prototype” can not be 100% pure if a “class” is used. Yes, its just a definition issue, but I personally want to emphasize on proto-objects, not on class morphing ones. In this regard I like the Io language (which unfortunately suffers from occasional halts in development and thus will have a hard time to become popular IMO)

  13. Benjamin Schweizeron 12 Sep 2008 at 6:37 pm

    Nice. Did you know http://benjamin-schweizer.de/exploiting-pythons-class-dispatcher.html ?

  14. Ianon 13 Sep 2008 at 4:58 pm

    In siblings() what you’re really comparing is identity not equality, so the `is` operator would be more explicit.

    And I certainly second the nod to CLOS, it does exactly this and tons more without support code.

  15. andrew cookeon 14 Sep 2008 at 2:54 am

    heh. cute. kudos. ignore the wankers - just leave them behind….

  16. sprachcaffe londraon 16 Sep 2008 at 4:28 am

    I am not surprısıng to anything. But thanks..

  17. Eduardo Padoanon 17 Sep 2008 at 7:16 pm

    I have tryied to emulate IO (iolanguage.com) prototyped OO model (it supports differential inheritance) in Python before:
    http://pastebin.com/f11edbd4e

  18. kroisse's me2DAYon 21 Sep 2008 at 9:04 pm

    Kr015se의 생각…

    Prototype-based OOP - in PYTHON!…

  19. eric casteleijnon 21 Oct 2008 at 2:36 pm

    Interesting. I think this is at best redundant though:

    if isinstance(method, Method) and method.object is not self:
    self._methods[name] = self._methods[name] = Method(method.method, self)

    Is that a ‘pasto’ or did you mean to have two different assignments there?
    You are doing a version on copy-on-read caching there for methods, if I’m not mistaken, so any changes in the method on the prototype are hidden after the first method call on the clone, or not?

  20. eric casteleijnon 21 Oct 2008 at 2:38 pm

    Interesting. Am I missing something obscure here:

    if isinstance(method, Method) and method.object is not self:
    self._methods[name] = self._methods[name] = Method(method.method, self)

    or is that a ‘pasto’ and did you mean to have two different assignments there?

    You are doing a version on copy-on-read caching there for methods, if I’m not mistaken, so any changes in the method on the prototype are hidden after the first method call on the clone, or not?

  21. Fredrik Holmströmon 22 Oct 2008 at 8:39 am

    eric: Yes you’re right, small mistake in my code and the first “self._methods[name] =” can safely be removed.

  22. Wynand Winterbachon 23 Oct 2008 at 11:11 am

    Very cool indeed.

    I’m implementing placeable support for translation messages (placeables represent things like formatting or variables that you don’t want a translator to see) and I keep on thinking that placeables should be modelled as prototypes (because their properties are not neatly heritable).

    A nice extension would be to support multiple dispatch. The (now defunct?) language Slate is a multiply dispatched prototype language and I think it allows for some truly elegant modelling.

  23. vsbnshjmon 29 Oct 2008 at 5:47 pm

    vsbnshjm…

    vsbnshjm…

  24. robon 03 Nov 2008 at 2:58 pm

    Hey man,

    great blog, some really good information im booking marking this and coming back a lot more. I too was a programmer so i know it can be restrictive in meeting new women in that environment, i now run pua classes which teaches guys how to approach and attract beautiful women with demonstrations right in front of your eyes, please check out the link.

    rob

Trackback URI | Comments RSS

Leave a Reply