Search code examples
pythondecorator

Class as decorator for class method


I want to use a decorator to do some preparation job and record the status the function have, so I write something like that:

class Decorator:
    def __init__(self, func):
        self.count = 0
        self.func = func

    def __call__(self, *args, **kwargs):
        self.count += 1 # Simply count the call times
        return self.func(self, *args, **kwargs)

class Foo:
    def __init__(self):
        self.value = 0
    
    @Decorator
    def test(self, value):
        self.value = value # change the value of instance
        print(self.value)

f = Foo()
f.test(1)

print(f.value)
print(f.test.value) 

But it's obvious that self in __call__(self, *args, **kwargs) corresponds to instance of Decorator instead of the instance of Foo , which will make f.value unchanged but f.test.value increase .

Is there any way I can pass the instance of Foo to Decorator instead of Decorator itself?

Or is there any way to implement this function much more clear?


Solution

  • As the decorator is only called once and replaces the method for all instance with one instance of the Decorator class. All it does is:

    Foo.test = Decorator(Foo.test)
    

    This makes it impossible to detect the instance called. One work-around would be to apply the decorator in the __init__ of Foo by hand:

    class Foo:
        def __init__(self):
            self.value = 0
            self.test = Decorator(self.test)
    
        def test(self, value):
            self.value = value # change the value of instance
            print(self.value)
    

    This way the decorator wraps the instance method, so you do not need to pass self in the __call__ of Decorator:

    class Decorator:
        def __init__(self, func):
            self.count = 0
            self.func = func
    
        def __call__(self, *args, **kwargs):
            self.count += 1 # Simply count the call times
            return self.func(*args, **kwargs)
    

    Now it works and you have to update you test method, as f.test.value no longer exists:

    f = Foo()
    f.test(1)
    
    print(f.value)
    

    It outputs two times a 1 as expected.