Python decorators in classes
Asked Answered
L

14

228

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.

Laws answered 11/8, 2009 at 23:1 Comment(0)
B
374

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
Broaddus answered 12/8, 2009 at 1:13 Comment(21)
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.Laws
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.Broaddus
Thanks Michael, only now saw that this is what I wanted.Laws
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.Perimeter
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?Stringed
@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 )Broaddus
What if the decorator needs access to instance variables? It is not possible because they don't exist when the decoration is substituted, right?Pointblank
This works, but is it proper form? Pycharm definitely doesn't seem to like it, I get tons of warnings.Steviestevy
@AndreiToroplean I thought it reasonable, but no idea what community consensus would be. I don't use pycharm, so I can't help with any errors there.Broaddus
@MichaelSpeer FYI pylint throws errors too on that... It seems really an unconventional thing to do, yet I can't find a more proper way to do it while keeping the decorator inside the class.Steviestevy
More specifically for instance: E0213: Method should have "self" as first argument (no-self-argument).Steviestevy
@AndreiToroplean I guess you'll either have to move your decorator outside of the class to adhere to your tooling, or try to get in a PR on your tooling that allows lacking self if the function is used as a decorator within the class. Good luck.Broaddus
@MichaelSpeer Would you please explain how does this work if bar returns a value? I am trying this out but my function returns a value and I cannot grab that.Smaragd
@Smaragd you'll need to capture the return from the wrapped function and return it from the wrapping function, ...; vv = foo( self ); print( "override bar out" ); return vv; ...Broaddus
One think I'd be interested in is if there's a way to define "class functions" (not class methods, just functions that happen to be defined inside of a class). So that these do not get bound as methods. _decorator should be such a "class function".Steviestevy
@AndreiToroplean that is what the staticmethod function does. the _decorator = staticmethod( _decorator ) line at the end of the class definition overwrites the regular binding that would become a method, and binds the same function but as a static method for use outside the class. for normal "class functions", which python calls static methods ( receiving neither self instance nor class instance ), you can just put @staticmethod before the def for the function you want to be static. We need the decorator above to be a regular function while decorating, hence binding at the endBroaddus
@MichaelSpeer I guess you are right, I was just taken aback by the fact that decorating the function directly as a staticmethod makes it unusable as a decorator itself. I'm wondering if it's a design choice, so that we don't define decorators this way. Or if this is something that may change in a future version of Python, to allow for this use case.Steviestevy
@AndreiToroplean classes just execute regular python in their block, then look to see what values were bound to create the class. class Wat: print( "unexpected" ) works just fine, if unexpectedly :). using the staticmethod decorator turns the function into an object that tells the class not to pass it self like it would a normal function. I don't know what check the class does to determine how to call the functions, but if it just special cases staticmethod, one could add a __call__ method to it that calls its contained __func__ to make it callable in the class blockBroaddus
@MichaelSpeer I tested the second sample code in Python 3.11 and found that even if I remove both the @staticmethod decorator on the _decorator method and the statement _decorator = staticmethod(_decorator), it works perfectly. However, this code does not work in Python 2.7 if either the @staticmethod decorator is used or the _decorator = staticmethod(_decorator) statement is removed. Does this mean that the new version of Python has changed the underlying mechanism?Chiromancy
@Chiromancy looks like python just started returning the function instead of <unbound method ...> objects with python 3.0. The concept of “unbound methods” has been removed from the language. When referencing a method as a class attribute, you now get a plain function object. So you don't have to staticmethod it to use it out of class, like in the 2.* series. The only difference being in 3.0 sans staticmethod, if you (grossly/horribly) used _decorator from an instance of the class to decorate in another class, it would get self injected into its args and break. so, ah, don't do that.Broaddus
@MichaelSpeer Thanks for revealing the details. Your explanation helped me understand the decorator deeply.Chiromancy
A
65

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)
Anthracosilicosis answered 11/8, 2009 at 23:33 Comment(2)
should probably be updated to reference the more accurate answer belowBergen
Nice. Your prose says impossible, but your code pretty much shows how to do it.Vesicle
T
47
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()
Terrarium answered 17/11, 2016 at 12:48 Comment(5)
TypeError: 'staticmethod' object is not callableWoodman
@Woodman don't call the decorator. For example, it should be @foo, not @foo()Manslaughter
Shouldn't the first argument to wrapper be self?Couperin
@Manslaughter That's not the problem. See https://mcmap.net/q/100354/-39-staticmethod-39-object-is-not-callable . 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.Narcho
@DominickPastore it works without even without being static method, but most modern IDEs/editors complain about having self as the first argument of the method. Let's say we do want to make the wrapper method static, we need to do so after the definition on any other method that uses this wrapper method, to avoid the automatic transformation to instance method. I would suggest playing with the debugger and placing breakpoints on the method definitions to understand this better. Also check the official docs for staticmethodFerrochromium
L
19

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.

Longs answered 7/6, 2016 at 9:43 Comment(0)
H
10

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)
Harwood answered 9/8, 2017 at 23:57 Comment(0)
I
7

I found this question while researching a very similar problem. My solution is to split the problem into two parts. First, you need to capture the data that you want to associate with the class methods. In this case, handler_for will associate a Unix command with handler for that command's output.

class OutputAnalysis(object):
    "analyze the output of diagnostic commands"
    def handler_for(name):
        "decorator to associate a function with a command"
        def wrapper(func):
            func.handler_for = name
            return func
        return wrapper
    # associate mount_p with 'mount_-p.txt'
    @handler_for('mount -p')
    def mount_p(self, slurped):
        pass

Now that we've associated some data with each class method, we need to gather that data and store it in a class attribute.

OutputAnalysis.cmd_handler = {}
for value in OutputAnalysis.__dict__.itervalues():
    try:
        OutputAnalysis.cmd_handler[value.handler_for] = value
    except AttributeError:
        pass
Injured answered 17/1, 2012 at 17:48 Comment(0)
B
7

I use this type of decorator in some debugging situations, it allows overriding class properties by decorating, without having to find the calling function.

class myclass(object):
    def __init__(self):
        self.property = "HELLO"

    @adecorator(property="GOODBYE")
    def method(self):
        print self.property

Here is the decorator code

class adecorator (object):
    def __init__ (self, *args, **kwargs):
        # store arguments passed to the decorator
        self.args = args
        self.kwargs = kwargs

    def __call__(self, func):
        def newf(*args, **kwargs):

            #the 'self' for a method function is passed as args[0]
            slf = args[0]

            # replace and store the attributes
            saved = {}
            for k,v in self.kwargs.items():
                if hasattr(slf, k):
                    saved[k] = getattr(slf,k)
                    setattr(slf, k, v)

            # call the method
            ret = func(*args, **kwargs)

            #put things back
            for k,v in saved.items():
                setattr(slf, k, v)

            return ret
        newf.__doc__ = func.__doc__
        return newf 

Note: because I've used a class decorator you'll need to use @adecorator() with the brackets on to decorate functions, even if you don't pass any arguments to the decorator class constructor.

Balaklava answered 20/8, 2012 at 9:13 Comment(0)
S
7

The simple way to do it. All you need is to put the decorator method outside the class. You can still use it inside.

def my_decorator(func):
    #this is the key line. There's the aditional self parameter
    def wrap(self, *args, **kwargs):
        # you can use self here as if you were inside the class
        return func(self, *args, **kwargs)
    return wrap

class Test(object):
    @my_decorator
    def bar(self):
        pass
Sanskritic answered 4/12, 2020 at 22:35 Comment(1)
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 attributeAlexanderalexandr
F
6

Declare in inner class. This solution is pretty solid and recommended.

class Test(object):
    class Decorators(object):
    @staticmethod
    def decorator(foo):
        def magic(self, *args, **kwargs) :
            print("start magic")
            foo(self, *args, **kwargs)
            print("end magic")
        return magic

    @Decorators.decorator
    def bar( self ) :
        print("normal call")

test = Test()

test.bar()

The result:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
Frum answered 12/11, 2019 at 9:6 Comment(0)
L
4

Decorators seem better suited to modify the functionality of an entire object (including function objects) versus the functionality of an object method which in general will depend on instance attributes. For example:

def mod_bar(cls):
    # returns modified class

    def decorate(fcn):
        # returns decorated function

        def new_fcn(self):
            print self.start_str
            print fcn(self)
            print self.end_str

        return new_fcn

    cls.bar = decorate(cls.bar)
    return cls

@mod_bar
class Test(object):
    def __init__(self):
        self.start_str = "starting dec"
        self.end_str = "ending dec" 

    def bar(self):
        return "bar"

The output is:

>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec
Lengthen answered 27/8, 2013 at 2:22 Comment(0)
O
3

I have a Implementation of Decorators that Might Help

    import functools
    import datetime


    class Decorator(object):

        def __init__(self):
            pass


        def execution_time(func):

            @functools.wraps(func)
            def wrap(self, *args, **kwargs):

                """ Wrapper Function """

                start = datetime.datetime.now()
                Tem = func(self, *args, **kwargs)
                end = datetime.datetime.now()
                print("Exection Time:{}".format(end-start))
                return Tem

            return wrap


    class Test(Decorator):

        def __init__(self):
            self._MethodName = Test.funca.__name__

        @Decorator.execution_time
        def funca(self):
            print("Running Function : {}".format(self._MethodName))
            return True


    if __name__ == "__main__":
        obj = Test()
        data = obj.funca()
        print(data)
Oberland answered 2/10, 2019 at 17:16 Comment(0)
P
2

Use a static method and include an additional parameter (self) in the inner function (wrapper) of the decorator.

class Test:

    @staticmethod
    def _decorator(f):

        @functools.wraps(f)
        def _wrapper(self, *args, **kwargs):
            # do some serious decorating (incl. calls to self!)
            print(self)
            return f(self, *args, **kwargs)

        return _wrapper

    @_decorator
    def bar(self):
        return 42
Pinnatifid answered 13/4, 2023 at 15:38 Comment(0)
W
1

You can decorate the decorator:

import decorator

class Test(object):
    @decorator.decorator
    def _decorator(foo, self):
        foo(self)

    @_decorator
    def bar(self):
        pass
Wingard answered 13/8, 2014 at 15:37 Comment(0)
S
0

For Python 3 and for the linters sake

def methoddecorator(deco: Callable[[Any, Callable], Callable]):
"""
Decorator to implement method decorators in the same class

Example of usage:

    class A:
        @methoddecorator
        def my_methods_deco(self, method):
            @wraps(method)
            def wrapper(this: 'A', *args, **kwargs):
                # do smth
                # N.B. for instance access use this, not self!
                return method(this, *args, **kwargs)
            return wrapper

        @my_methods_deco
        def my_method(self, a, b):
            ...

"""
@functools.wraps(deco)
def wrapped_deco(method):
    return deco(NotImplemented, method)
return wrapped_deco

Use this uber-decorator to patch the classes.

BTW, this code does not support decorator parameters like @deco(param=...), but more complicated one does.

def methoddecorator(deco):
"""
Decorator to implement method decorators in the same class
Supports optionally parametrized decorators

Example of usage:

    class A:
        @methoddecorator
        def my_methods_deco(self, _method=None, param1=None, param2=None):
            @wraps(method)
            def wrapper(this: 'A', *args, **kwargs):
                # do smth
                # deco params are also available here
                return method(this, *args, **kwargs)
            return wrapper

        @my_methods_deco
        def my_method1(self, a, b):
            ...

        @my_methods_deco(param1=11, param2=12)
        def my_method2(self, a, b):
            ...

"""
@wraps(deco)
def wrapped_deco(_method=None, **kwargs):
    return (
        deco(NotImplemented, _method)
        if _method is not None
        else partial(deco, NotImplemented, **kwargs)
    )
return wrapped_deco
Sweetheart answered 20/9, 2023 at 17:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.