30

I have a class with a dull repeating pattern on their functions and I wanted to turn this pattern into a decorator. But the thing is that this decorator must access some attributes of the current instance, so I wanted to turn it into a method in this class. I'm having some problems with that.

So, this is similar to what I want:

class DullRepetitiveClass:
    def __init__(self, nicevariable):
        self.nicevariable = nicevariable

    def mydecorator(self, myfunction):
        def call(*args, **kwargs):
            print "Hi! The value of nicevariable is %s"%(self.nicevariable,)
            return myfunction(*args, **kwargs)
        return call

    @mydecorator            #Here, comment (1) below.
    def somemethod(self, x):
        return x + 1

(1) Here is the problem. I want to use the DullRepetitiveClass.mydecorator method to decorate the somemethod method. But I have no idea how to use the method from the current instance as the decorator.

Is there a simple way of doing this?

EDIT: Ok, the answer is quite obvious. As Sven puts it below, the decorator itself just transform the method. The method itself should deal with all things concerning the instance:

def mydecorator(method):
    def call(self, *args, **kwargs):
        print "Hi! The value of nicevariable is %s"%(self.nicevariable,)
        return method(self, *args, **kwargs)
    return call


class DullRepetitiveClass:
    def __init__(self, nicevariable):
        self.nicevariable = nicevariable

    @mydecorator            
    def somemethod(self, x):
        return x + 1
Rafael S. Calsaverini
  • 13,582
  • 19
  • 75
  • 132
  • 1
    Please fix your indentation. Also note that your code does not contain any class methods. – Sven Marnach Jul 31 '12 at 12:57
  • Are you asking how to pass a *different* instance other than `self` to `mydecorator` as it decorates `somemethod`? – kojiro Jul 31 '12 at 13:00
  • Oops. Copying from vim and pasting to the browser can be a pain sometimes... – Rafael S. Calsaverini Jul 31 '12 at 13:00
  • Although not accessing the instance, using a callable class as a decorator then enables state to be saved in the class between invocations. This doesn't answer this question but may give a lead to someone coming across this question for another purpose. – NeilG Apr 28 '21 at 05:58

2 Answers2

19

The decorator gets only one parameter – the function or method it decorates. It does not get passed an instance as self parameter – at the moment the decorator is called, not even the class has been created, let alone an instance of the class. The instance will be passed as first argument to the decorated function, so you should include self as first parameter in the parameter list of call().

I don't see the necessity to include the decorator in the class scope. You can do this, but you can just as well have it at module scope.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 2
    What if your decorator needs to use class members? Or is that just a bad idea / pattern? – aaronlevin Jul 31 '12 at 14:07
  • @weirdcanada: Since the class has not yet been created at the time the decorator is called, you cannot access its members in an ordinary fashion. You can do some hacks to work around this (things like passing `locals()` as parameter to the decorator), but they are rather non-obvious, and I can't think of a good reason to do this. – Sven Marnach Jul 31 '12 at 14:19
  • I have an example of such a case. Let me find it. – aaronlevin Jul 31 '12 at 15:04
  • OK, I had a `@property` called `maxima` and another called `minima`. When they were called, though, I needed to check if they had been calculated. Rather than writing that logic twice, I wrote a wrapper that basically started with `if self.new_values is None` and then calculated the `maxima` and `minima` accordingly, setting `self.new_values` to `False` when it was done. I was kind of uncomfortable with it in the beginning, as I defined `def new_values_for_extrema_validator(method)` and then defined the wrapper within as `def wrapper(self, *args, **kwargs)`. So, it references self within. – aaronlevin Jul 31 '12 at 15:08
  • The above is meant for @SvenMarnach. – aaronlevin Jul 31 '12 at 15:09
  • 1
    @weirdcanada: What you did is exactly what I suggested in my answer. You are only accessing *instance* members inside the *wrapper* function, which is fine. My last comment was regarding accessing *class* members inside the *decorator* function, which is something completely different. (I'll also write a minor comment to your gist.) – Sven Marnach Aug 01 '12 at 10:16
1

Yes it can, and you can do it like this:

class DullRepetitiveClass:
    def __init__(self, nicevariable):
        self.nicevariable = nicevariable

    def mydecorator(myfunction):
        def call(self, *args, **kwargs):
            self.nicevariable += 'X'
            print(f"Nicevariable is now {self.nicevariable}")
            return myfunction(self, *args, **kwargs)
        return call
    
    @mydecorator
    def somemethod(self, x):
        print(f'{x=}')
        return x + 1

d = DullRepetitiveClass('start')
d.somemethod(3)
d.somemethod(4)

which produces

Nicevariable is now startX
x=3
Nicevariable is now startXX
x=4

Or you can set the decorator yourself instead of relying on the syntactic sugar provided by Python via the @ operator

class DullRepetitiveClass:
    def __init__(self, nicevariable):
        self.nicevariable = nicevariable
        self.somemethod = self.mydecorator(self.somemethod)

    def mydecorator(self, myfunction):
        def call(*args, **kwargs):
            self.nicevariable += 'X'
            print(f"Nicevariable is now {self.nicevariable}")
            return myfunction(*args, **kwargs)
        return call

    def somemethod(self, x):
        return x + 1

Notice how call does not get self in its parameters (print out args if you like to check), but the call to myfunction(*args, **kwargs) does inject it so somemethod gets it.

On the other hand, if you put the decorator outside the class, you could still access the object:

def mydecorator(myfunction):
    def call(self, *args, **kwargs):
        self.nicevariable += 'X'
        print(f"Nicevariable is now {self.nicevariable}")
        return myfunction(self, *args, **kwargs)
    return call


class DullRepetitiveClass:
    def __init__(self, nicevariable):
        self.nicevariable = nicevariable

    @mydecorator
    def somemethod(self, x):
        return x + 1

In this case self the call function receives self

Personally, I prefer to include the decorator within the class because it operates on its specific attributes and looks more self-contained.

Pynchia
  • 10,996
  • 5
  • 34
  • 43