I've got a class and would like to apply a decorator to all functions within that class without having to add a function decorator to every single function. I know that there are solutions like the ones explained here How to decorate all functions of a class without typing it over and over for each method? to add a decorator to the entire class. However, I need to have access to all class attributes within the decorator.
So using the decorator from the other solution like so, I'd need to be able to access all of cls's properties in the f
def function_decorator(orig_func):
def decorator(*args, **kwargs):
print("Decorating wrapper called for method %s" % orig_func.__name__)
result = orig_func(*args, **kwargs)
return result
return decorator
def class_decorator(decorator):
def decorate(cls):
# this doesn't work
# print(cls.name)
for attr in cls.__dict__:
if callable(getattr(cls, attr)):
setattr(cls, attr, decorator(getattr(cls, attr)))
return cls
return decorate
@class_decorator(function_decorator)
class PersonWithClassDecorator:
def __init__(self, name):
self.name = name
def print_name(self):
print(self.name)
me = PersonWithClassDecorator("Me")
me.print_name()
The cls variable is of type <class 'main.PersonWithClassDecorator'>.
Does anyone have an idea on how to achieve this? I also looked into metaclasses but ran into the same issue of not being able to access the attributes of my class. Any help is much appreciated :)
Alternative 1
As @Nullman suggested, you can move the decorating into the
__getattribute__-hook. This method is called whenever any attibute (including methods) are accessed from an instance of your class.You can implement
__getattribute__directly in your class or create a mixin that only contains the decorating.And then inherit from
Decorator.class PersonWithClassDecorator(Decorate): ...This simple approach has two downsides:
function_decoratoris hardcoded into theDecorateclass__init__,__str__or all the operator hooks are not decorated (internally they are not accessed from the instance, but from the class)Note: if you use
mypy(you should ;-) ) the__getattribute__hook disables the detection of non-existing attributes. To solve this, wrap the__getattribute__definition in aif not typing.TYPE_CHECKINGblock.Alternative 2
The
function_decoratoractually has access to instance attributes. You can change it accordingly and use your original approach.Note: i added type hints and replaced the old
%-based string formatting withf-strings.This has a small problem:
self.nameis only defined after__init__was called. You can handle this in different ways:getattr(self, 'name', None)-self.nameis thenNonebefore init__init__:if orig_func.__name__ == '__init__': return orig_func__init__Downsides:
@classmethodand@staticmethodfunction_decoratoris now coupled to your class