pythonpython-decorators

Define a decorator as a method inside class


I'm trying to create a method inside my class that counts the complete run of a specific function. I want to use a simple decorator. I found this reference and rewrite this simple script:

class myclass:
    def __init__(self):
        self.cnt = 0

    def counter(function):
        """
        this method counts the runtime of a function
        """
        def wrapper(self, **args):
            function(**args)
            self.counter += 1
        return wrapper


@myclass.counter
def somefunc():
    print("hello from somefunc")


if __name__ == "__main__":
    obj = myclass()
    # or if comment @myclass.counter
    # somefunc = myclass.counter(somefunc)
    somefunc()

And of course, I get:

TypeError: wrapper() missing 1 required positional argument: 'self'

I tried to rewrite the counter as a class method:

class myclass:
    def __init__(self):
        self.cnt = 0

    def counter(self, function):
        """
        this method counts the runtime of a function
        """
        def wrapper(**args):
            function(**args)
            self.cnt += 1
        return wrapper


def somefunc():
    print("hello from somefunc")


if __name__ == "__main__":
    obj = myclass()
    somefunc = obj.counter(somefunc)
    for i in range(10):
        somefunc()
        print(obj.cnt)

Which works fine but I think it is not a valid decorator definition. Is there any way to define the decorator inside the class method and pass the self-argument to its function? or defining a decorator inside a class is useless?

EDIT:------ First I can't define the decoration outside of the class method. Second I'm trying to make a scheduled class that runs a specific function (as input) for a fixed interval and a specific amount of time so I need to count it.


Solution

  • So I was able to draft up something for you, below is the code:

    def count(func):
        def wrapper(self):
            TestClass.call_count += 1
            func(self)
    
        return wrapper
    
    
    class TestClass(object):
        call_count = 0
    
        @count
        def hello(self):
            return 'hello'
    
    
    if __name__ == '__main__':
        x = TestClass()
        for i in range(10):
            x.hello()
    
        print(TestClass.call_count)
    

    Why would it cause problems to have the decorator in a class:

    It's not straight forward to have a decorator function inside the class. The reasons are below:

    Reason 1 Every class method must take an argument self which is the instance of the class through which the function is being called. Now if you make the decorator function take a self argument, the decorator call @count would fail as it get converted to count() which doesn't pass the self argument and hence the error:

    TypeError: wrapper() missing 1 required positional argument: 'self'

    Reason 2 Now to avoid that you can make your decorator as static by changing the declaration like below:

    @staticmethod
    def count(func):
        pass
    

    But then you have another error:

    TypeError: 'staticmethod' object is not callable

    Which means you can't have a static method as well. If you can't have a static method in a class, you have to pass the self instance to the method but if you pass the self instance to it, the @count decorator call wouldn't pass the self instance and hence it won't work.

    So here is a blog that explains it quite well, the issues associated with it and what are the alternatives.

    I personally prefer the option to have a helper class to hold all my decorators that can be used instead of the only class in which it's defined. This would give you the flexibility to reuse the decorators instead of redefining them which would follow the ideology

    code once, reuse over and over again.