prototype-based inheritance in Python
Kragen Sitaker
kragen at dnaco.net
Sat Sep 23 00:24:06 EDT 2000
Yesterday, on comp.lang.python, someone was playing around with adding
C#-like (Delphi-like, C++Builder-like, JavaBeans-like) properties to
Python in a few lines of code, using __getattr__ and __setattr__.
I'd had a conversation with Chris Olds a few weeks ago, speculating on
the feasibility of implementing Self-like prototype-based inheritance
in Python.
So I was inspired to do it. See test() at the bottom for one way to
use it; you should be able to use this Object class as a mixin, too,
but I haven't tried it.
It needs at least Python 1.5.2 to work; 1.5.1 chokes on a couple of
things.
I disclaim any copyright in this code. If someone is going to have
license hassles with it down the line, it won't be from me, and I think
my employer would have a hard time claiming I wrote it as part of my
job. ;)
# Simple implementation of Self-like prototype-based inheritance.
# By Kragen Sitaker, 2000-09-22.
# Sorry if it's unpythonic or crude; I don't know Python very well.
#
# Ideally we'd like to be able to assign methods dynamically.
# This means our "methods" must really be functions.
# It would be nice if we could just use native Python method
# objects; unfortunately, they are harder to declare (you must do it at top
# level of a class) and suffer from restrictions intended to make them safer:
# they (apparently) can't be rebound to point to a new instance, and they
# (apparently) can't be called on instances that don't inherit from their
# real class.
# However, because of lambda arg binding and __call__able objects,
# it is possible to reimplement all of the standard method-call machinery in
# Python:
class Method:
def __init__(self, instance, function):
self.instance = instance
self.function = function
def __call__(self, *args):
nargs = list(args)
nargs[0:0] = [self.instance]
return apply(self.function, nargs)
def prototype_method_rebind(self, instance):
return Method(instance, self.function)
# For now, we only handle inheritance for getting.
# Self handled it for setting, too; if you inherited an attribute from somebody,
# you setting that attribute would set it in the somebody, not in you. That's
# unpythonic, so we don't do that.
class Object:
def setproto(self, other):
self.__prototype = other
# what a nasty rat's nest. Surely there is a simpler way to write this
# function.
def __getattr__(self, name):
try:
prototype = self.__dict__['_Mixin__prototype']
except KeyError:
raise AttributeError, name
except:
raise
result = getattr(prototype, name)
try:
result_rebind = result.prototype_method_rebind
except AttributeError: # it's not a Method object, return it unchanged
return result
except: # I don't think this can happen
raise
return result_rebind(self)
# we want to recognize when someone tries to bind functions to properties
# and turn them into Method objects
def __setattr__(self, name, newval):
if callable(newval):
self.__dict__[name] = Method(self, newval)
else:
self.__dict__[name] = newval
TestFailure = "test failed"
# If this function runs to completion instead of raising an exception, that
# means the class more or less works.
def test():
x = Object()
ok = 0
try:
x.baz
except AttributeError:
ok = 1
except:
raise
if not ok:
raise TestFailure, "undefined attribute was found"
x.spam = 3
if x.spam != 3:
raise TestFailure, "setting a number didn't work"
x.foot = lambda self, other: self.spam + other
if x.foot.__class__ != Method:
raise TestFailure, "setting a method didn't work"
if x.foot(4) != 7:
raise TestFailure, "Johnny can't add"
y = Object()
y.setproto(x)
if y.foot(5) != 8:
raise TestFailure, "delegation didn't work"
y.spam = 40
if y.foot(5) != 45:
raise TestFailure, "couldn't set y.spam"
if x.foot(4) != 7:
raise TestFailure, "broke x messing with y"
--
<kragen at pobox.com> Kragen Sitaker <http://www.pobox.com/~kragen/>
Perilous to all of us are the devices of an art deeper than we ourselves
possess.
-- Gandalf the Grey [J.R.R. Tolkien, "Lord of the Rings"]
More information about the Python-list
mailing list