Decorator metaclass
Thomas Karolski
Thomas.Karolski at googlemail.com
Fri May 23 15:25:19 EDT 2008
Turns out the first msg I sent did not reach the list, so I'll just post
what I've achieved by now:
------------------------------------------
class DecoratorDummy(object): pass
class InheritedDecoratorType(type):
def __new__(cls, name, bases, dct):
# return if its a class which inherited from Decorator
if Decorator in bases:
return type.__new__(cls, name, bases, {})
# if its a class which did not directly inherit from Decorator,
# then it inherited from a class which has been manipulated using the
# Decorator class.
# in that case we change the bases of the inheriting class.
# We'll split the manipulated class into Decorator and its implementation
for b in bases:
if type(b) is InheritedDecoratorType:
break
newbases = [x for x in bases]
# remove the manipulated base class
newbases.remove(b)
# and add the impl of the manipulated base class
newbases.append(b._impl_type)
# and add the Decorator class
newbases.append(Decorator)
# now we'll have to make sure the dict of the new class shows the
original base class
# (which has been removed) as the implementation class
dct[b.__name__] = b._impl_type
# since we have added a Decorator class, we ought to get rid of it
# through the DecoratorType metaclass
r = DecoratorType.__new__(cls, name, tuple(newbases), dct)
return r
class DecoratorType(type):
def __new__(cls, name, bases, dct):
# if the first class is DecoratorDummy, then we're handling the
# Decorator class, which is not supposed to be modified
if bases[0] is DecoratorDummy:
return type.__new__(cls, name, bases, {})
# if one of the bases is the Decorator class
b = [x for x in bases]
if Decorator in b:
# create a new impl type which inherits from every but the decorator
class
b.remove(Decorator)
impl_type = type('%sImpl'%name, tuple(b), dict(dct))
# make the returned type no longer a DecoratorType, but rather a
normal type
# Types which inherit from a class which inherited from Decorator,
will thus
# *not* be put through this metaclass.
#dectype = type.__new__(type, name, tuple(b), {'_impl_type' : impl_type })
dectype = type.__new__(InheritedDecoratorType, name, tuple(b),
{'_impl_type' : impl_type })
# update the old class to implement this implementation
def __init__(self, *args, **dargs):
new_impl = impl_type(*args, **dargs)
super(dectype._impl_type, new_impl).__init__(*args,
**dargs)
object.__setattr__(self, '_impl', new_impl)
def decorator(self):
return object.__getattribute__(self, '_impl')
def __getattribute__(self, attr):
if attr=="decorator":
return object.__getattribute__(self, 'decorator')
# if we have a specified method inside the decorator().__decorate__ var,
# then call decorator().attr(), otherwise proxy the call
d = object.__getattribute__(self, 'decorator')()
if attr in d.__decorate__:
return getattr(d, attr)
return getattr(d.getParent(), attr)
dectype.__init__ = __init__
dectype.decorator = decorator
dectype.__getattribute__ = __getattribute__
return dectype
class Decorator(DecoratorDummy):
__metaclass__ = DecoratorType
class Window(object):
def __init__(self, parent):
print "Window::__init__(%s)"%self
self._parent = parent
def setParent(self, parent):
self._parent = parent
def getParent(self):
return self._parent
class Textarea(Window):
def draw(self):
print "Textarea::draw()"
class HBar(Decorator, Window):
__decorate__ = ["draw"]
def __init__(self, parent):
print "HBar::__init__(%s)"%self
Window.__init__(self, parent=parent)
self._progress = 0.0
def setProgress(self, p):
print "setting progress to %s"%p
self._progress= p
def getProgress(self):
return self._progress
def draw(self):
self.getParent().draw()
print "HBar::draw()"
class HBarTrue(HBar):
# HBar's bases: Window (Decorator removed by metaclass)
# HBar's methods: __init__, decorator, __getattribute__
# everything else is inside decorator()
# we thus need to make calls to self within HBarTrue,
# calls to self.decorator() - this is not possible
# Otherwise we could also let HBarTrue inherit from HBarImpl,
# however then a call to HBar.__init__ would be invalid inside
# HBarTrue.__init__ unless we specify HBar internally as being HBarImpl
# - this however is not possible
# if we inherit from HBarImpl, then we no longer have the decorator
# functionality. We'd thus have to include Decorator in the list of bases
# Inherit normally from HBar and not Decorator.
# Move all methods from HBarTrue to HBar.decorator()
# create a custom __init__ method which calls HBar.decorator().__init__
def __init__(self, parent):
print "HBarTrue::__init__(%s)"%self
for each in dir(self):
print each
HBar.__init__(self, parent)
self.setProgress(0.0)
myTextarea = Textarea("main.parent")
myTextarea.draw()
myTextarea = HBarTrue(myTextarea)
myTextarea.draw()
myTextarea.decorator().setProgress(100.0)
------------------------------------------
The code above works only if I don't inherit from HBar like I did.
As it is now, the HBar class, which inherits from Decorator and Window,
is going to be manipulated by the metaclass. During this process the
Decorator base-class is removed, the implementation of HBar moved into
_impl_type and the methods __init__, decorator and __getattribute__ are
being assigned to the new class.
Everything works so far if I don't subclass HBar.
If I do subclass HBar, then the subclass goes through the second
metaclass (InheritedDecoratorType). In there I replace the base class
HBar with HBarImpl and Decorator. This way the subclass inherits the
implementation of HBar and through the Decorator class then the subclass
goes through the same metaclass magic as HBar in the first step. Of
course however, since HBarTrue is no longer a subclass of HBar (since
that baseclass has been removed), the HBar.__init__ method won't accept
the non HBar self parameter.
Now the reason why I'm using decorators, is because I want to be ably to
add the functionality dynamicly - without the need of construction
classes for the different possibilities. Composite pattern does not help
in this case, since I lose the ability to choose in what order I call
the decorator's and the decorated's methods.
I'll just keep trying. Any input greatly appreciated.
Regards,
Thomas K.
More information about the Python-list
mailing list