Search code examples
pythonclassmethodsdecorator

Decorating class method at runtime


I want to decorate a class method, but at runtime (by that I mean that the decorator does not need to be specified before the method with the @ notation)

See this example of a standard method decorator:

def method_decorator(func):
    def decorated(self, *args, **kwargs):
        print('decorator:', args, kwargs)
        return func(self,*args, **kwargs)
    return decorated

class Standard():
    @ method_decorator
    def decorated(self, *args, **kwargs):
        print('decorated: ', args, kwargs)

s = Standard()
s.decorated(1,2)

Result:

decorator: (1, 2) {}
decorated:  (1, 2) {}

So I tried different ways to do it at runtime:

class RunTime():
    def set_decorator_1(self, decorator):
        self.decorated = decorator(self.decorated)
    def set_decorator_2(self, decorator):
        RunTime.decorated = decorator(RunTime.decorated)
    def set_decorator_3(self, decorator):
        self.decorated = decorator(RunTime.decorated)
    def set_decorator_4(self, decorator):
        setattr(self, 'decorated', decorator(RunTime.decorated))
    def set_decorator_5(self, decorator):
        setattr(self, 'decorated', decorator(self.decorated))
    def decorated(self, *args, **kwargs):
        print('decorated: ', args, kwargs)

r = RunTime()
r.set_decorator_*(method_decorator)
r.decorated(1,2)

And here are the outputs:

  1. The decorator is not properly decorated:
decorator: (2,) {}
decorated:  (1, 2) {}
  1. Works as expected, but when set_decorator is called, all RunTime instances are also decorated, which I want to avoid, because I only want to decorate the method of a single instance.
  2. Bad decoration
decorator: (2,) {}
decorated:  (2,) {}
  1. same as 3
  2. same as 1

I also tries another decorator, which works well (with set_decorator_1) but does not allow me to access self in it:

def method_decorator_runtime(func):
    def decorated( *args, **kwargs):
        print('decorator: ', args, kwargs)
        return func( *args, **kwargs)
    return decorated

Does anyone know a proper way of decorating a method at run time, with ability to access self in the decorator ?


Solution

  • The closest solution I have found for your question is to use a decorator with an argument where you can pass the instance of the object that has its method decorated.

    def method_decorator(self_eventually):
        def decorator(func):
            def decorated(*args, **kwargs):
                print('decorator:', repr(self_eventually), args, kwargs)
                return func(*args, **kwargs)
            return decorated
        return decorator
    
    class RunTime():
    
        def set_decorator(self, decorator):
            self.decorated = decorator(self)(self.decorated)
    
        def decorated(self, *args, **kwargs):
            print('decorated:', repr(self), args, kwargs)
    
    r = RunTime()
    r.set_decorator(method_decorator)
    r.decorated(1, 2)