define a decorator as method inside class

Question:

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.

Asked By: Masoud Rahimi

||

Answers:

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.

Answered By: iam.Carrot

Your second code example is functionally equivalent to a standard decorator. The standard decorator syntax is just a shorthand for the same thing, namely, reassigning a function value equal to a closure (a function pointer with arguments predefined), where the closure is your decorator wrapper holding the original as its predefined argument.

Here’s an equivalent with standard syntax. Notice you need to create the counter class instance in advance. The decorator syntax refers to that instance, because it must indicate the specific object which holds your counter, rather than just the class of the object:

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

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

global counter_object
counter_object = myclass()

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

if __name__ == "__main__":
    for i in range(10):
        somefunc()
        print(counter_object.cnt)
Answered By: MichaelsonBritt
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.