It will be very useful to have a general-purpose utility, that can turn any decorator for functions, into decorator for methods. I thought about it for an hour, and actually come up with one:
from typing import Callable Decorator = Callable[[Callable], Callable] def decorate_method(dec_for_function: Decorator) -> Decorator: def dec_for_method(unbounded_method) -> Callable: # here, `unbounded_method` will be a unbounded function, whose # invokation must have its first arg as a valid `self`. When it # return, it also must return an unbounded method. def decorated_unbounded_method(self, *args, **kwargs): @dec_for_function def bounded_method(*args, **kwargs): return unbounded_method(self, *args, **kwargs) return bounded_method(*args, **kwargs) return decorated_unbounded_method return dec_for_method
The usage is:
# for any decorator (with or without arguments) @some_decorator_with_arguments(1, 2, 3) def xyz(...): ... # use it on a method: class ABC: @decorate_method(some_decorator_with_arguments(1, 2, 3)) def xyz(self, ...): ...
Test:
def dec_for_add(fn): """This decorator expects a function: (x,y) -> int. If you use it on a method (self, x, y) -> int, it will fail at runtime. """ print(f"decorating: {fn}") def add_fn(x,y): print(f"Adding {x} + {y} by using {fn}") return fn(x,y) return add_fn @dec_for_add def add(x,y): return x+y add(1,2) # OK! class A: @dec_for_add def f(self, x, y): # ensure `self` is still a valid instance assert isinstance(self, A) return x+y # TypeError: add_fn() takes 2 positional arguments but 3 were given # A().f(1,2) class A: @decorate_method(dec_for_add) def f(self, x, y): # ensure `self` is still a valid instance assert isinstance(self, A) return x+y # Now works!! A().f(1,2)