4

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.

Masoud Rahimi
  • 5,785
  • 15
  • 39
  • 67
  • Possible duplicate of [Python decorators count function call](https://stackoverflow.com/questions/44968004/python-decorators-count-function-call) Also you can check out what is `self` and `__init__` [here](https://micropyramid.com/blog/understand-self-and-__init__-method-in-python-class/) – iam.Carrot Apr 23 '18 at 05:20
  • @iam.Carrot Thanks, but my decorator definition must be inside of my class (a method or whatever) because it must count for my schedule. – Masoud Rahimi Apr 23 '18 at 05:23
  • what exactly you mean by `it must count for my schedule`? – iam.Carrot Apr 23 '18 at 05:24
  • @iam.Carrot I am creating a scheduled class that run a specific function as input for specific times. I need to count the runtime of function inside and terminate the schedule afterward. – Masoud Rahimi Apr 23 '18 at 05:26
  • do you want to maintain the count for all instances or you want to handle the count for a single instances. For example, if I create 10 instances, and call the method 5 times from each instance, do you want the count to be 50 or just 5? – iam.Carrot Apr 23 '18 at 05:28
  • @iam.Carrot As I edited the main question it is a scheduler library based on APScheduler so I only create a single instance of my class and add multiple functions as input to it (of course the counter should be a list). I add functions as a job to the scheduler. – Masoud Rahimi Apr 23 '18 at 05:33
  • `I can't define the decoration outside of the class method` Any particular reason for this? – iam.Carrot Apr 23 '18 at 05:50
  • @iam.Carrot I'm creating a standalone method to use on other scripts so I thought it would be better to define it inside the class. – Masoud Rahimi Apr 23 '18 at 05:53
  • It's not possible to have a decorator inside a class. My answer is the closest you can get. I am working on (typing) providing a reason as to why is it so. – iam.Carrot Apr 23 '18 at 05:54

2 Answers2

6

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.

iam.Carrot
  • 4,976
  • 2
  • 24
  • 71
  • I think the definition of sample function(hello here) must be outside of a class because it must be anything in the future. – Masoud Rahimi Apr 23 '18 at 05:57
  • I think otherwise, I'll tell you why. The decorator `count` is the actual function that's doing the job. So, tomorrow you can have a bunch of different methods and they all can be decorated as `@count` and you don't necessarily edit the same function. If it's anything I would recommend having different methods – iam.Carrot Apr 23 '18 at 06:04
  • I'm not sure but my other class methods must be in the class itself, the input function must be a single function or many functions and I don't know them. so I just need to create an instance of my class and start the schedule and the counter variable counts inside the class and all the stuff do in there watch my second example in the question. – Masoud Rahimi Apr 23 '18 at 06:17
  • 1
    I've updated the answer with the reasons. Also you can go through the link I've shared to know more on why it's not possible and what are the alternatives. – iam.Carrot Apr 23 '18 at 06:21
  • @iam.Carrot., regarding "Why isn't it possible to have the decorator in a class", would you regard my answer as a demonstration of how to do this, declaring a decorator function inside a class (assuming the decorator class instance is created in advance)? – MichaelsonBritt Apr 23 '18 at 06:33
  • @MichaelsonBritt I would rather pick declaring all decorators in a `Helper Class` that contains all decorators that can be used instead of the same class. 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). Although the code does seem to work and hence the upvote. But would it be advisable to have such an implementation? Considering it's forcing you to create an object instance before hand even if the class is not being used. – iam.Carrot Apr 23 '18 at 06:43
  • @iam.Carrot, I'm not suggesting one coding pattern over another. But the statement "Why isn't it possible to have the decorator in a class" could be misleading and might be edited for clarity. The blog post you linked explains that it is possible to have decorators in a class definition, with various ways of achieving it, and the pros and cons of each. – MichaelsonBritt Apr 23 '18 at 06:49
  • 1
    @MichaelsonBritt I've updated the answer. I hope it resolves the conflict in the two statements between the blog and the answer. I do appreciate the quick heads up on this. Please do let me know if I can improve it further. – iam.Carrot Apr 23 '18 at 06:55
3

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)
MichaelsonBritt
  • 976
  • 6
  • 9