Search code examples
pythonpython-3.xpython-decoratorspython-class

Decorate all function in a class by using metaclass


Lets say my class has many function, and I want to apply my decorator on each one of them. I have researched for a while, and find https://stackoverflow.com/a/6307917/18859252. By using metaclass, I can decorate all function in one line. Here is my code (framework)

class myMetaClass(type):
   def __new__(cls, name, bases, local):
      for attr in local:
         value = local[attr]
         if callable(value) and attr != '__init__':
            local[attr] = log_decorator(local['Variable'], value)
      return super().__new__(cls, name, bases, local)


class log_decorator():
   def __init__(self, Variable, func):
       self.Variable = Variable
       self.func = func

   def __call__(self, *args, **kargs):
       start_time = time.time()
       self.func(*args, **kargs)
       end_time = time.time()


class Test(metaclass = myMetaClass):

   Variable = Some_Class
   check_test = Some_Class

   def __init__(self, **args):
       self.connect = Some_Class(**args)

   def A(self, a, b):
       self.connect.abc
       pass

then use like this

def Flow():
   test = Test(**args)
   test.A(a, b)

But here is problem, it show exception like:

TypeError:A() missing 1 required positional argument: 'self'

I have no idea about this problem. I'd be very grateful if anyone has an answer or if there is a better way.


Solution

  • The piece you are missing (and the bit I don't fully understand, but has to do with functions or methods as descriptors and how python will attach an instance as the self parameter) is that log_decorator() is an instance of that class and not a function or method (even though you have defined a __call__() method which makes it callable.)

    Here is some code which just slightly changes the syntax needed, but gives you the results you want:

    import functools
    
    class log_decorator:
        def __init__(self, Variable):  # Note that the only parameter is Variable
           self.Variable = Variable
    
        def __call__(self, func):
            @functools.wraps(func)
            def decorated(*args, **kwargs):
               start_time = time.time()
               func(*args, **kwargs)
               end_time = time.time()
    
            return decorated
    
    class myMetaClass(type):
       def __new__(cls, name, bases, local):
          for attr in local:
             value = local[attr]
             if callable(value) and attr != '__init__':
                # Note the change in syntax vvv
                local[attr] = log_decorator(local['Variable'])(value)
          return super().__new__(cls, name, bases, local)