Python decorators in classes

150,598

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)
Share:
150,598

Related videos on Youtube

hcvst
Author by

hcvst

Updated on June 24, 2021

Comments

  • hcvst
    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
    hcvst almost 15 years
    Thanks 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
    Michael Speer almost 15 years
    The 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
    hcvst over 13 years
    Thanks Michael, only now saw that this is what I wanted.
  • mgiuca
    mgiuca about 12 years
    I 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
    extraeee over 10 years
    I 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
    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
    Nathan Buesgens almost 7 years
    should probably be updated to reference the more accurate answer below
  • wyx
    wyx over 5 years
    TypeError: 'staticmethod' object is not callable
  • Mad Physicist
    Mad Physicist about 5 years
    Nice. Your prose says impossible, but your code pretty much shows how to do it.
  • docyoda
    docyoda over 4 years
    @wyx don't call the decorator. For example, it should be @foo, not @foo()
  • CpILL
    CpILL about 4 years
    Shouldn't the first argument to wrapper be self?
  • Dr_Zaszuś
    Dr_Zaszuś over 3 years
    What 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
    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. Had wrapper = staticmethod(wrapper) occurred first (or had the more usual @staticmethod decorator been used), it would indeed give a TypeError. I'm not actually sure what making it a static method accomplishes in this case.
  • weegolo
    weegolo about 2 years
    Putting 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