34

Here's an example.

class myclass:
    def __init__(self):
        self.start = False
        
    def check(self):
        return not self.start
    
    def doA(self):
        if self.check():
            return
        print('A')
        
    def doB(self):
        if self.check():
            return
        print('B')

As you see, I want to write the check action in a decorator way, but after i tried many times, I found I can only write the method outside my class. Please teach me how to write it inside the class.

I can write the code in this way:

def check(func):
    def checked(self):
        if not self.start:
            return
        func(self)
    return checked

class myclass:
    def __init__(self):
        self.start = False
        
    @check
    def doA(self):
        print('A')
        
    @check
    def doB(self):
        print('B')
        

a = myclass()

a.doA()
a.doB()

a.start = True

a.doA()
a.doB()

but I don't think it's a good practice, I want to defined the check method inside my class.

Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Max
  • 7,957
  • 10
  • 33
  • 39
  • I'm not clear what you're asking. Could you rephrase it, or perhaps provide an example? – Kirk Strauser Dec 13 '12 at 02:49
  • Your question still isn't clear. Your first code example doesn't include any decorator usage. What are you trying to use as a decorator, and if it's not working, what is happening instead? Do you get an error? If so, what error? Show the actual code that produces that error. – BrenBarn Dec 13 '12 at 03:20
  • So you want to use decorators inside a class on one (or more) of the member methods? If this is so, I understand what you want, I dont understand why you want it? There's no compelling reason for this. a `class` is a tidy container, You don't ever need decorators as methods of the class you can just call one method from another. Is this a case of getting carried away by `decorators` ? – Srikar Appalaraju Dec 13 '12 at 03:21

2 Answers2

64

While I don't think this is completely needed all the time, here is how you would make a decorator within your class. It is a little more cumbersome because of the way the methods become bound to the self later on. Normally with plain function decorators you don't have to worry about that.

The requirements are:

  1. The decorator needs to be defined first before the methods that will use it
  2. It needs to use functools.wraps to preserve the bound methods properly

Example:

from functools import wraps

class myclass:
    def __init__(self):
        self.start = False

    def _with_check(f):
        @wraps(f)
        def wrapped(inst, *args, **kwargs):
            if inst.check():
                return
            return f(inst, *args, **kwargs)
        return wrapped

    def check(self):
        return self.start

    @_with_check
    def doA(self):
        print('A')

    @_with_check
    def doB(self):
        print('B')

I made it a protected member since it is not really something someone else needs to use outside of the class. And it still preserves your public check() call for use by itself. The decorator simply wraps calling it first before calling the target method.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • 6
    if i defined a method in class without self as first parameter , pydev will give an error, Method 'check - test' should have self as first parameter ,but it can properly be run, i was tricked. – Max Dec 13 '12 at 03:57
  • 1
    @Max @jdi why not decorate the decorator with `@staticmethod`? I know it looks exessive but that would fix it. –  Aug 31 '18 at 14:08
  • 3
    @J.C.Rocamonde Then you will have an error "TypeError: 'staticmethod' object is not callable" – Piotr Wasilewicz Nov 17 '18 at 19:39
  • To be more specific, it is because the decorator is going to be used *before* the class is fully defined, so the staticmethod decorator will not yet have evaluated. It really needs to be a plain function which is why I said it is cumbersome to define it inside the class. – jdi Nov 17 '18 at 19:46
  • 1
    it will work with pure @staticmethod if the defining class is a subclass of a class defining `check()` and the function is decorated as `parent.decorator`. see https://gist.github.com/cowbert/f357391fd2d140f817e8d90e781bd15d (I use the pattern on py27, which fits with 2012 OP) – cowbert Mar 28 '19 at 21:46
  • don't you need a ```self``` argument in ```_with_check```? – Tim May 01 '19 at 14:46
  • 1
    @Tim no I already answered that question in my previous comment. The decorator gets applied to methods before the class has turned it into a bound method. – jdi May 02 '19 at 19:43
  • @jdi I see! Interesting interaction. Also is @wraps(f) necessary? Not sure of the behavior in a class. – Tim May 02 '19 at 21:50
  • @Tim well yes as per the second point in my answer, it needs it to preserve the name and docstring of the original method. Otherwise all your methods would just have the name 'wrapped' in their representation, without docstrings – jdi May 03 '19 at 23:53
  • What is `inst`? – Abhijit Sarkar Dec 24 '19 at 22:12
  • @AbhijitSarkar `inst` is just `self`. It even could have been called `self` if that makes it more clear. Because we are wrapping a method with a decorator, we need to accept the implicit `self` instance as the first argument. – jdi Dec 25 '19 at 23:17
1

If you have a need to modify how many or all methods on a class are called, but you only want to apply that change in behavior to that class and its subclasses, just use __getattr__() and/or __getattribute__() to perform your behavior instead.

Silas Ray
  • 25,682
  • 5
  • 48
  • 63