Reputation: 10709
I've a class that inherits decorated properties from its parent. I want to add one more decorator (@thedecorator1("yyy")
), but without overriding the whole method and existing decorators. (Order of decorators is important to some extent, thedecorator1 should be always between property.setter and thedecorator2)
Is that possible in Python 3 ?
from functools import wraps
class thedecorator1:
def __init__ (self, idx):
self.idx = idx
def __call__ (self, func):
@wraps(func)
def wrapped(*args, **kwargs):
print("thedecorator1", self.idx, args)
return func(*args, **kwargs)
return wrapped
def thedecorator2(func):
def _thedecorator2(self, *args, **kwargs):
print("thedecorator2",args)
return func(self, *args, **kwargs)
return _thedecorator2
class SomeClass:
"""That's te base class"""
_some_property = None
@property
@thedecorator2
def someproperty(self):
return self._some_property
@someproperty.setter
@thedecorator1("xxx")
@thedecorator2
def someproperty(self, value):
self._some_property = value
class AnotherClass(SomeClass):
"""That's what works, but I need to copy everything everytime"""
_some_property = None
@property
@thedecorator2
def someproperty(self):
return self._some_property
@someproperty.setter
@thedecorator1("yyy")
@thedecorator1("xxx")
@thedecorator2
def someproperty(self, value):
self._someproperty = value
class YetAnotherClass(SomeClass):
"""That's what I think I need"""
dosomethingsmart(target = someproperty, decorator = thedecorator1("yyy"), after=someproperty.setter)
Upvotes: 1
Views: 1653
Reputation: 251383
There is no way to do this, because a decorator does not "know" about any other decorators applied before or after it. A decorator is not extra information attached to a function; rather, when you use the decorator, it replaces the original function with the decorated version. It's not like you have a function with a list of individual decorators; you just get the end result of applying all the decorators, squashed into one object, and you can't later peek inside and know which decorators are in effect. So when you write this:
class SomeClass:
"""That's te base class"""
_some_property = None
@property
@thedecorator2
def someproperty(self):
return self._some_property
Now SomeClass.someproperty
is a property object. That property object wraps thedecorator2
, but property
doesn't know anything about thedecorator2
; it just wraps the object its given. You defined a method called someproperty
, but the decorators wrap it and "bury" the original function; there is no general way to "unwind" the series of decorators to get access to the original function or to the partially-decorated intermediate functions (e.g., the result of applying thedecorator2
but not property
).
Later when you define AnotherClass, you say you want to "add a decorator". But to what? The only thing you have access to is SomeClass.someproperty
. The original function you defined is not available; it is "buried" under the decorators, and you can't get it back out.
Now, for some decorators, you may be able to get the original function back out. For instance, a property
stores its getter function in its fget
attribute. But how (or if!) a decorator stores the original function is up to that decorator. Your thedecorator2
decorator, for instance, does not expose func
in any way. So if AnotherClass
knows exactly which decorators were applied originally, it might be able to unwind them using specific knowledge about each one. But this still requires you to know which decorators were applied initially, so it doesn't save you anything in terms of not having to duplicate that information in AnotherClass
.
The bottom line is that decorators do not "save" the original thing they decorate; they just squash it into whatever they produce. There is no general way to "peel off" the decorators of a function that has been decorated, or even to know which decorators have been applied. Hence you can't have something like insert_new_decorator(somefunction, after=somedecorator)
, because there is no way to peel back to the layers of decoration to know where somedecorator
was in the stack.
Decorators aren't like clothes, where you can take off an outer jacket put a different shirt on underneath, and then put the original jacket back on. Rather, imagine something like a strawberry dipped in chocolate, then dipped in whipped cream, and then drizzled with icing. You can't "unwrap" the cream and the icing and to put a different chocolate coating on underneath. The entire thing is melded into one object whose layers can't be pulled apart.
Upvotes: 2
Reputation: 168626
Consider this program:
from functools import wraps
def dec1(fn):
@wraps(fn)
def wrap(*args, **kw):
print "dec1", fn.__name__
return fn(*args, **kw)
return wrap
def dec2(fn):
@wraps(fn)
def wrap(*args, **kw):
print "dec2", fn.__name__
return fn(*args, **kw)
return wrap
class C(object):
prop = None
@property
def foo(self):
return self.prop
@foo.setter
@dec1
def foo(self, x):
self.prop = x
class D(C):
foo = property(
C.foo.fget,
dec2(C.foo.fset),
C.foo.fdel)
print '.'
c=C()
c.foo = 7
print '.'
d = D()
d.foo = 8
print '.'
D.foo is a property that inherits the get and delete functions from C.foo, but wraps the set function.
An alternative definition for D
is:
class D(C):
foo = C.foo.setter(dec2(C.foo.fset))
In either case, the output of the program is:
.
dec1 foo
.
dec2 foo
dec1 foo
.
Note that the assignment to c.foo
invokes one wrapper while the assignment to d.foo
invokes both.
Upvotes: 0