Search code examples
pythonpython-3.xpython-decorators

Decorate operators python3.5


I'm trying to decorate all methods in class and i succeded with this code, but i'm also trying to log calls to operators like * + - / , is there any way to decorate them or something like getattr(self,"*") to log the calls ?


class Logger(object):
  def __init__(self, bool):
      self.bool = bool

  def __call__(self, cls):
      class DecoratedClass(cls):
          def __init__(cls, *args, **kwargs):
              super().__init__(*args, **kwargs)

              if not(self.bool):
                  return
              methods = [func for func in dir(cls)
                      if callable(getattr(cls, func))
                      and not func.startswith("__class")]
              for func in methods:
                  old_func = getattr(cls, func)
                  def decorated_function(fname, fn):
                      def loggedFunction(*args, **kwargs):
                          print("Calling {0} from {3} with params {1} and kwargs {2}".format(fname.upper(), args, kwargs, cls))
                          return fn(*args, **kwargs)
                      return loggedFunction
                  setattr(cls, func, decorated_function(func, old_func))

      return DecoratedClass

@Logger(True)
class DummyClass():
  def __init__(self,foo):
      self.foo = foo
  def bar(self):
      print(self.foo)
  def __mul__(self,other):
      print("Hello",other)
if __name__ == '__main__':
  a = DummyClass('hola')
  a.method()
  a.__mul__(a) #this is logged 
  print(a*a) #this is not logged by decorator


Solution

  • Thanks to Łukasz, here is a working script.

    A difficulty I encountered is to handle multiple instances and avoid to decorate multiple times the same class methods. To handle this problem, I keep track of the decorated class methods (cls.__logged).

    Another difficulty is to deal with the magic methods like __setattr__, __getattribute__, __repr__, ... My solution is to ignore them, except for a list that you must define at start (loggable_magic_methods).

    from functools import wraps
    
    
    loggable_magic_methods = ['__mul__',]
    
    
    def is_magic_method(method):
        return method.startswith('__')
    
    
    class Logger(object):
        def __init__(self, bool):
          self.bool = bool
    
        def __call__(self, cls):
    
            class LoggedClass(cls):
                cls.__logged = []
                def __init__(instance, *args, **kwargs):
                    super().__init__(*args, **kwargs)
    
                    if not(self.bool):
                        return
    
                    methods = [funcname for funcname in dir(instance)
                               if callable(getattr(instance, funcname))
                               and (funcname in loggable_magic_methods or not is_magic_method(funcname))]
    
                    def logged(method):
                        @wraps(method)
                        def wrapper(*args, **kwargs):
                            print (method.__name__, args, kwargs, cls)
                            return method(*args, **kwargs)
                        return wrapper
    
                    for funcname in methods:
                        if funcname in cls.__logged:
                            continue
    
                        if is_magic_method(funcname):
                            setattr(cls, funcname, logged(getattr(cls, funcname)))
                            cls.__logged.append(funcname)
                        else:
                            setattr(instance, funcname, logged(getattr(instance, funcname)))
            return LoggedClass
    
    @Logger(True)
    class DummyClass():
        def __init__(self, foo, coef):
            self.foo = foo
            self.coef = coef
        def bar(self):
            print(self.foo)
        def __mul__(self, other):
            print(self.foo)
            print(other.foo)
            return self.coef * other.coef
    
    if __name__ == '__main__':
        a = DummyClass('hola', 1)
        a.bar()
        print()
        print(a.__mul__(a))
        print()
        print(a*a)
        print()
        b = DummyClass('gracias', 2)
        b.bar()
        print()
        print(b.__mul__(a))
        print()
        print(b*a)