The top answer for Class method decorator with self arguments? (direct link to answer: https://stackoverflow.com/a/11731208) describes how a decorator can access attributes of the object when decorating a method.
I tried to apply this to my own code, but realized it only works when using a function as the decorator. For some reason, when using a class as the decorator, the arguments tuple no longer contains the self
object. Why does this happen, and is there some other way to access the wrapped method's self
?
To demonstrate:
import functools
class DecoratorClass:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
def __call__(self, *args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
print("Counting with:", args[0].name)
self.func(*args, **kwargs)
def decorator_func(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("args:", args)
print("kwargs:", kwargs)
print("Counting with:", args[0].name)
func(*args, **kwargs)
return wrapper
class Counter:
def __init__(self, name, count):
self.name = name
self.count = count
@decorator_func
def count_a(self):
for i in range(self.count):
print(i)
@DecoratorClass
def count_b(self):
for i in range(self.count):
print(i)
c = Counter('my counter', 3)
c.count_a()
print()
c.count_b()
Output:
args: (<__main__.Counter object at 0x7f4491a21970>,)
kwargs: {}
Counting with: my counter
0
1
2
args: ()
kwargs: {}
Traceback (most recent call last):
File "./test2.py", line 48, in <module>
c.count_b()
File "./test2.py", line 14, in __call__
print("Counting with:", args[0].name)
IndexError: tuple index out of range
It's a known problem (see here). I actually ran into this same issue when implementing a class decorator in one of my projects a while back.
To fix, I added the below method to my class - which you can also add to your DecoratorClass
, and then it should all work without surprises.
def __get__(self, instance, owner):
"""
Fix: make our decorator class a decorator, so that it also works to
decorate instance methods.
https://stackoverflow.com/a/30105234/10237506
"""
from functools import partial
return partial(self.__call__, instance)
Also, do see the linked SO answer for an example of why this is.