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_decorator
is hardcoded into theDecorate
class__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_CHECKING
block.Alternative 2
The
function_decorator
actually 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.name
is only defined after__init__
was called. You can handle this in different ways:getattr(self, 'name', None)
-self.name
is thenNone
before init__init__
:if orig_func.__name__ == '__init__': return orig_func
__init__
Downsides:
@classmethod
and@staticmethod
function_decorator
is now coupled to your class