Python decorators in classes
Solution 1
Would something like this do what you need?
class Test(object):
def _decorator(foo):
def magic( self ) :
print "start magic"
foo( self )
print "end magic"
return magic
@_decorator
def bar( self ) :
print "normal call"
test = Test()
test.bar()
This avoids the call to self to access the decorator and leaves it hidden in the class namespace as a regular method.
>>> import stackoverflow
>>> test = stackoverflow.Test()
>>> test.bar()
start magic
normal call
end magic
>>>
edited to answer question in comments:
How to use the hidden decorator in another class
class Test(object):
def _decorator(foo):
def magic( self ) :
print "start magic"
foo( self )
print "end magic"
return magic
@_decorator
def bar( self ) :
print "normal call"
_decorator = staticmethod( _decorator )
class TestB( Test ):
@Test._decorator
def bar( self ):
print "override bar in"
super( TestB, self ).bar()
print "override bar out"
print "Normal:"
test = Test()
test.bar()
print
print "Inherited:"
b = TestB()
b.bar()
print
Output:
Normal:
start magic
normal call
end magic
Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic
Solution 2
What you're wanting to do isn't possible. Take, for instance, whether or not the code below looks valid:
class Test(object):
def _decorator(self, foo):
foo()
def bar(self):
pass
bar = self._decorator(bar)
It, of course, isn't valid since self
isn't defined at that point. The same goes for Test
as it won't be defined until the class itself is defined (which its in the process of). I'm showing you this code snippet because this is what your decorator snippet transforms into.
So, as you can see, accessing the instance in a decorator like that isn't really possible since decorators are applied during the definition of whatever function/method they are attached to and not during instantiation.
If you need class-level access, try this:
class Test(object):
@classmethod
def _decorator(cls, foo):
foo()
def bar(self):
pass
Test.bar = Test._decorator(Test.bar)
Solution 3
import functools
class Example:
def wrapper(func):
@functools.wraps(func)
def wrap(self, *args, **kwargs):
print("inside wrap")
return func(self, *args, **kwargs)
return wrap
@wrapper
def method(self):
print("METHOD")
wrapper = staticmethod(wrapper)
e = Example()
e.method()
Solution 4
This is one way to access(and have used) self
from inside a decorator
defined inside the same class:
class Thing(object):
def __init__(self, name):
self.name = name
def debug_name(function):
def debug_wrapper(*args):
self = args[0]
print 'self.name = ' + self.name
print 'running function {}()'.format(function.__name__)
function(*args)
print 'self.name = ' + self.name
return debug_wrapper
@debug_name
def set_name(self, new_name):
self.name = new_name
Output (tested on Python 2.7.10
):
>>> a = Thing('A')
>>> a.name
'A'
>>> a.set_name('B')
self.name = A
running function set_name()
self.name = B
>>> a.name
'B'
The example above is silly, but it works.
Solution 5
Here's an expansion on Michael Speer's answer to take it a few steps further:
An instance method decorator which takes arguments and acts on a function with arguments and a return value.
class Test(object):
"Prints if x == y. Throws an error otherwise."
def __init__(self, x):
self.x = x
def _outer_decorator(y):
def _decorator(foo):
def magic(self, *args, **kwargs) :
print("start magic")
if self.x == y:
return foo(self, *args, **kwargs)
else:
raise ValueError("x ({}) != y ({})".format(self.x, y))
print("end magic")
return magic
return _decorator
@_outer_decorator(y=3)
def bar(self, *args, **kwargs) :
print("normal call")
print("args: {}".format(args))
print("kwargs: {}".format(kwargs))
return 27
And then
In [2]:
test = Test(3)
test.bar(
13,
'Test',
q=9,
lollipop=[1,2,3]
)
start magic
normal call
args: (13, 'Test')
kwargs: {'q': 9, 'lollipop': [1, 2, 3]}
Out[2]:
27
In [3]:
test = Test(4)
test.bar(
13,
'Test',
q=9,
lollipop=[1,2,3]
)
start magic
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-3-576146b3d37e> in <module>()
4 'Test',
5 q=9,
----> 6 lollipop=[1,2,3]
7 )
<ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs)
11 return foo(self, *args, **kwargs)
12 else:
---> 13 raise ValueError("x ({}) != y ({})".format(self.x, y))
14 print("end magic")
15 return magic
ValueError: x (4) != y (3)
Related videos on Youtube
hcvst
Updated on June 24, 2021Comments
-
hcvst about 3 years
Can one write something like:
class Test(object): def _decorator(self, foo): foo() @self._decorator def bar(self): pass
This fails: self in @self is unknown
I also tried:
@Test._decorator(self)
which also fails: Test unknown
I would like to temporarily change some instance variables in the decorator and then run the decorated method, before changing them back.
-
hcvst almost 15 yearsThanks for your reply. Yes this would work if it wasn't for the fact that I wanted the decorator to perform some ops on the instance variables - and that would require self.
-
Michael Speer almost 15 yearsThe decorator or the decorated function? Note the returned "magic" function that wraps bar is receiving a self variable above when "bar" is called on an instance and could do anything to the instance variables it wanted before and after ( or even whether or not ) it called "bar". There is no such thing as instance variables when declaring the class. Did you want to do something to the class from within the decorator? I do not think that is an idiomatic usage.
-
hcvst over 13 yearsThanks Michael, only now saw that this is what I wanted.
-
mgiuca about 12 yearsI find this solution much nicer than the accepted answer, because it allows the use of @ decorator syntax at the point of definition. If I have to put decorator calls at the end of the class, then it isn't clear that the functions are being decorated. It's a bit weird that you can't use @staticmethod on the decorator itself, but at least it works.
-
extraeee over 10 yearsI dont think it works if I create a inherited class of Test.For example: class TestB(Test): @_decorator def foobar(self): print "adsf" Is there a workaround?
-
Michael Speer over 10 years@extraeee: check the edit I made. you need to qualify the given decorator as a staticmethod, but only after you're done using it ( or assigning the staticmethod version to a different name )
-
Nathan Buesgens almost 7 yearsshould probably be updated to reference the more accurate answer below
-
wyx over 5 yearsTypeError: 'staticmethod' object is not callable
-
Mad Physicist about 5 yearsNice. Your prose says impossible, but your code pretty much shows how to do it.
-
docyoda over 4 years@wyx don't call the decorator. For example, it should be
@foo
, not@foo()
-
CpILL about 4 yearsShouldn't the first argument to
wrapper
beself
? -
Dr_Zaszuś over 3 yearsWhat if the decorator needs access to instance variables? It is not possible because they don't exist when the decoration is substituted, right?
-
Dominick Pastore almost 3 years@docyoda That's not the problem. See stackoverflow.com/q/41921255 . The saving grace in this example is that
wrapper = staticmethod(wrapper)
is below@wrapper
. Hadwrapper = staticmethod(wrapper)
occurred first (or had the more usual@staticmethod
decorator been used), it would indeed give aTypeError
. I'm not actually sure what making it a static method accomplishes in this case. -
weegolo about 2 yearsPutting the decorator outside the class doesn't answer the question, which was how to put a decorator inside a class. One example of where your approach wouldn't work is where the decorator depends on a class attribute