KIDJourney
KIDJourney

Reputation: 1220

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?

Upvotes: 2

Views: 1023

Answers (3)

nicosnow
nicosnow

Reputation: 1

class threadSafeGenerator(object):
    """docstring for threadSafeGenerator"""
    class SafeGenerator(object):
        """docstring for SafeGenerator"""
        def __init__(self, iterable):
            self.iterable = iterable
            self.lock = Lock()
        def __iter__(self):
            return self

        def __next__(self):
            with self.lock:
                return next(self.iterable)

    def __init__(self, func):
        super(threadSafeGenerator, self).__init__()
        self.func = func

    def __call__(self, *args, **kwargs):
        return self.SafeGenerator(self.func(self, *args, **kwargs))

I found using Priyesh Kumar's answer that you can simply pass the self argument from the call method to the function being decorated:

def __call__(self, *args, **kwargs):
    return self.SafeGenerator(self.func(self, *args, **kwargs))

hope this helps!

EDIT: Never mind only works if the function being passed through the decorator does not call class variables defined in the init method

Upvotes: 0

Priyesh Kumar
Priyesh Kumar

Reputation: 2857

I got this here

import functools

class Decorator(object):
    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)

    def __get__(self, instance, instancetype):
        """Implement the descriptor protocol to make decorating instance 
        method possible.
        """

        # Return a partial function with the first argument is the instance 
        #   of the class decorated.
        return functools.partial(self.__call__, instance)



class Foo:
    def __init__(self):
        self.value = 0

    @Decorator
    def test(self, value):
        self.value = value # change the value of instance



f = Foo()
f.test(3)
print(f.value)  # prints 3


g = Foo()
g.test(8)
print(g.value) # prints 8

or May be this

def preJob(function):
    def updateToDo(self, *args, **kwargs):
        # do some recording
        function(self, *args, **kwargs)
    return updateToDo

class Foo(object):
    def __init__(self):
        self.value = 0

    @preJob
    def test(self, value):
        self.value = value

f = Foo()
f.test(3)
print(f.value)  # prints 3


g = Foo()
g.test(8)
print(g.value) # prints 8

Upvotes: 1

Leon
Leon

Reputation: 3036

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.

Upvotes: 4

Related Questions