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.
Interesting. Clever use of the __pos__ method !
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.
Please never use __double_leading_and_trailing_underscore__ for method and attribute names http://www.python.org/dev/peps/pep-0008/
Yawn. Get back when you’ve tried CLOS, or any other generic-function multiple-dispatch object system.
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.
Really clever!
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.
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.
Why not try __metaclass__ keyword for same purpose?
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 ;)
[…] Love and Theft » Blog Archive » Prototype based programming in python (tags: programming design Python prototype) […]
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)
Nice. Did you know http://benjamin-schweizer.de/exploiting-pythons-class-dispatcher.html ?
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.
heh. cute. kudos. ignore the wankers - just leave them behind….
I am not surprısıng to anything. But thanks..
I have tryied to emulate IO (iolanguage.com) prototyped OO model (it supports differential inheritance) in Python before:
http://pastebin.com/f11edbd4e
Kr015se의 생각…
Prototype-based OOP - in PYTHON!…
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?
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?
eric: Yes you’re right, small mistake in my code and the first “self._methods[name] =” can safely be removed.
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.
vsbnshjm…
vsbnshjm…
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