1

I have a problem with a Python decorator.

class decorator(object):
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        print('sth to log: %s : %s' % (self.function.__name__, args))
        return self.function(*args, **kwargs)


@decorator
def sum_test(a, b):
    print('sum: %s' % (a+b))

@decorator
class Class_test(object):
    def __init__(self):
        pass
    def sum_func(self, a, b):
        print('class sum: %s' % (a+b))
        return a+b

if __name__ == '__main__':
    sum_test(3, 4)
    func = Class_test()
    var1 = func.sum_func(1, 4)
    print(var1)

Output:

sth to log: sum_test : (3, 4)
sum: 7
sth to log: Class_test : ()
class sum: 5
5

The decorator is working as I want for function sum_test. I can log that function sum_test was used with variables 3 and 4.

I have a problem with decorating classes. I can log that object of class Class_test was created, but I don't have information that function sum_func was used.

Why is __call__() in decorator was not triggered when running sum_func on class object and it was triggered when used directly on function sum_test?

martineau
  • 119,623
  • 25
  • 170
  • 301
Pablo
  • 13
  • 3
  • 2
    **Side Note:** I recommend using closures inside of functions instead of a full-blown **class**. – Eb946207 Jan 08 '19 at 21:26
  • What do you want the decorator to do when applied on a class? Is it meant to decorate every method of the class? – blhsing Jan 08 '19 at 21:30
  • Generally speaking you can't apply the same decorator to both functions and classes—although it might be possible with some clever coding. – martineau Jan 08 '19 at 22:33
  • I want to have decorator with logging which functions and classes where used with variables. Should I have another decorator for class? Could I have another decorator only for function of class sum_func? – Pablo Jan 09 '19 at 09:27

1 Answers1

0

Decorating the class with decorator means that Class_test is now a decorator object such that Class_test.function is the class you wrote a body for.

Remember, the @decorator syntax is syntactic sugar for

class Class_test(object):
    def __init__(self):
        pass
    def sum_func(self, a, b):
        print('class sum: %s' % (a+b))
        return a+b

Class_test = decorator(Class_test)

(Except that at no point does the Class_test symbol refer to your class).

So this decorator object is called when you call Class_test, which is exactly what you see happen in your log:

sth to log: Class_test : ()

func.sum_func, on the other hand, is a regular bound method. It doesn't know anything about decorator.

If you're going to use it inside classes, I would prefer a closure-style decorator (a function that returns a function). The function that it returns will get registered a a method normally by the class machinery.

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('sth to log: %s : %s' % (func.__name__, args))
        return func(*args, **kwargs)
    return wrapper

class Class_test(object):
    @decorator
    def sum_func(self, a, b):
        print('class sum: %s' % (a+b))
        return a+b

If you still want to use the class-based approach, read Python Class Based Decorator with parameters that can decorate a method or a function

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • Should I have different decorator for class? I have issue when decorating directly function in the class: return self.function(*args, **kwargs) TypeError: sum_func() missing 1 required positional argument: 'b' How to tell decorator to return function to that class? – Pablo Jan 09 '19 at 13:12
  • @Pablo I added a section to my answer to address that. – Patrick Haugh Jan 09 '19 at 13:36