Reputation: 17
My goal is to be able to adding decorator dynamically to all method of class, before and after instantiation.
I need to use self inner my decoractor added after class is instanciate in variable.
It is working when I add decoractor to class method directly
but not for instantiate class method.
In my example, I have 2 test class, one wrapper apply_trigger
, which apply the active_trigger
wrapper to all methods of class or instantiate class :
def active_trigger(func):
@wraps(func)
def wrapper(*args, **kwargs):
def afterexec(obj: object, func,*args, **kwargs):
a = func(obj,*args, **kwargs)
# process on obj => SELF
print(obj)
return a
return afterexec(args[0], func, *args[1:], **kwargs)
return wrapper
def apply_trigger(cls):
for name, m in inspect.getmembers(cls, lambda x: inspect.isfunction(x) or inspect.ismethod(x)):
setattr(cls , name, active_trigger(m))
return cls
class test1():
def method_test1(self, a:int):
print(self)
print(a)
@apply_trigger
class test2():
def method_test2(self,b:int, *args, **kwargs):
print(b)
def method_t2ex(self,b:int):
print(b,'_EXEC_')
I need to add decorator to all methods dynamically . In the case of test2, it is working
t2 = test2()
t2.method_test2(2)
but in the case of decorator added after instanciation, there are no 'self' pass in the decorator active_trigger
t1 = apply_trigger(test1())
t1.method_test1(11)
My debugger show at the wrapper step wrapper(*args, **kwargs)
:
for the test2 case. args
: (<__main__.test2 object at 0x7feae10bdf90>,2)
for the test1 case. args
: (11)
How get the self
attribut in test1 while decorator execution ?
Thanks to the explanation of @juanpa.arrivillaga
I had find a way to make with 2 distinct decorator when I have a class or an instance.
I use a decorator with parameter in the case of instance, where I pass the instance as parameter.
def active_trigger(func):
@wraps(func)
def wrapper(*args, **kwargs):
def afterexec(obj: object, func,*args, **kwargs):
a = func(obj,*args, **kwargs)
# process on obj => SELF
print(obj)
return a
return afterexec(args[0], func, *args[1:], **kwargs)
return wrapper
def active_trigger_instance(self):
def active_trigger(func):
@wraps(func)
def wrapper(*args, **kwargs):
def afterexec(func,*args, **kwargs):
a = func(*args, **kwargs)
print(self)
return a
return afterexec(func, *args, **kwargs)
return wrapper
return active_trigger
def apply_trigger(cls):
import inspect
if inspect.isclass(cls):
for name, m in inspect.getmembers(cls, lambda x: inspect.isfunction(x) or inspect.ismethod(x)):
setattr(cls , name, active_trigger(m))
else:
for name, m in inspect.getmembers(type(cls), lambda x: inspect.isfunction(x) or inspect.ismethod(x)):
setattr(cls , name, active_trigger_instance(cls)(getattr(cls, name)))
return cls
now it is working but maybe there a more elegant way to make it
Upvotes: 0
Views: 92
Reputation: 96236
In Python, a method is simply a function that lives in the class namespace, that when called on an instance, receives the instance it is called on as the first positional argument
In other words,
some_instance.some_method(arg, x=11)
Is roughly equivalent to:
type(some_instance).some_method(some_instance, arg, x=11)
For the above magic1 to work then some_method
must exist in the class namespace (or in the namespace of a class in the method resolution order -- that is inheritance).
It doesn't matter where the function is defined, it matters in what namespace it is in:
>>> class MyClass:
... pass
...
>>> my_instance = MyClass()
>>>
>>> def a_method(self):
... return 42
...
>>> MyClass.a_method = a_method
>>>
>>> my_instance.a_method()
42
Note the namespaces:
>>> from pprint import pprint
>>> pprint(MyClass.__dict__)
mappingproxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>,
'a_method': <function a_method at 0x105a301f0>})
>>> pprint(my_instance.__dict__)
{}
Now, the following will demonstrate that the instance will not be passed to the function if the function exists in the instance namespace instead of the class namespace
>>> def another_method(self):
... return 88
...
>>> my_instance.another_method = another_method
>>> my_instance.another_method()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: another_method() missing 1 required positional argument: 'self'
>>>
And note the namespaces again:
>>> pprint(MyClass.__dict__)
mappingproxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>,
'a_method': <function a_method at 0x105a301f0>})
>>> pprint(my_instance.__dict__)
{'another_method': <function another_method at 0x105a30280>}
The above is essentially what you are doing when you do:
t1 = apply_trigger(test1())
Just that *args, **kwargs
in your def wrapper
definition is preventing a TypeError
.
Now, if you want to do this on an instance, my first suggestions is simply not to, and reconsider your design. But if you really want, you can just create an auxilliary function which does the binding of the first argument manually (you could use functools.partial
),
def apply_trigger_to_instance(obj):
...
for name, m in inspect.getmembers(cls, lambda x: inspect.isfunction(x) or inspect.ismethod(x)):
bound_m = functools.partial(m, obj)
setattr(cls , name, active_trigger(m))
1 If you want to understand it so it isn't magic, you have to understand the descriptor protocol
Upvotes: 0