Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorate operators python3.5

Tags:

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

like image 521
Matias Junemann Avatar asked Nov 23 '16 06:11

Matias Junemann


People also ask

How do you decorate a function in Python?

To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function.

What are decorators in Python 3?

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.

How do you declare a decorator in Python?

To create a decorator function in Python, I create an outer function that takes a function as an argument. There is also an inner function that wraps around the decorated function. To use a decorator ,you attach it to a function like you see in the code below.

What does __ add __ do in Python?

__add__ magic method is used to add the attributes of the class instance. For example, let's say object1 is an instance of a class A and object2 is an instance of class B and both of these classes have an attribute called 'a', that holds an integer.


2 Answers

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)
like image 81
Frodon Avatar answered Oct 11 '22 13:10

Frodon


Currently you are patching values on instance. Your usage of cls in __init__ signature is false friend - actually it's old plain self in this case.

If you want to override magic methods, interpreter looks for them on class objects, not on instances.

Minimal example:

class DummyClass:
    def __init__(self, foo):
        self.foo = foo
    def __mul__(self, other):
        return self.foo * other.foo


def logged(method):
    def wrapper(*args, **kwargs):
        print (method.__name__, args, kwargs)
        return method(*args, **kwargs)
    return wrapper

DummyClass.__mul__ = logged(DummyClass.__mul__)


a = DummyClass(1)
b = DummyClass(2)
assert a * a == 1
assert a * b == 2
assert b * b == 4

Each call is logged.

>>> a = DummyClass(1)
>>> b = DummyClass(2)
>>> assert a * a == 1
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BFEB8>) {}
>>> assert a * b == 2
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BF080>) {}
>>> assert b * b == 4
__mul__ (<__main__.DummyClass object at 0x00000000011BF080>, <__main__.DummyClass object at 0x00000000011BF080>) {}

I'll leave a task of rewriting monkey-patching approach to you.

like image 23
Łukasz Rogalski Avatar answered Oct 11 '22 12:10

Łukasz Rogalski