I have this class method decorator from https://stackoverflow.com/a/46361833/5316326 that accepts parameters.
How can I list the class methods that use this decorator on the created object?
from functools import update_wrapper, partial
def my_decorator(name):
class MyDecorator(object):
def __init__(self, func):
update_wrapper(self, func)
self.func = func
def __get__(self, obj, objtype):
"""Support instance methods."""
return partial(self.__call__, obj)
def __call__(self, obj, *args, **kwargs):
return self.func(obj, *args, **kwargs)
return MyDecorator
class MyClass(object):
t=12
#my_decorator("hee")
def my_method(self, a):
print(a+self.t)
The following attempt works, but by parsing the partial function to a string, there should be a correct implementation:
def find_my_decorators(cls):
def g():
for name in dir(cls):
attr = getattr(cls, name)
if isinstance(attr, partial) and 'MyDecorator' in str(attr.func):
yield name
return [name for name in g()]
It gives, however, the desired results:
>>> find_my_decorators(MyClass)
['my_method']
>>> m = MyClass()
>>> m.my_method(a=12)
>>> find_my_decorators(m)
['my_method']
It works both on the class as the object.
Related
Consider the following decorator function, which either returns a decorated function, or a parametrized decorator function:
from functools import wraps, partial, update_wrapper
from inspect import signature
def wrapit(func=None, *, verb='calling'):
if func is None: # return a decoratOR
return partial(wrapit, verb=verb)
else: # return a decoratED
#wraps(func)
def _func(*args, **kwargs):
print(f'{verb} {func.__name__} with {args} and {kwargs}')
return func(*args, **kwargs)
return _func
Demo:
>>> f = lambda x, y=1: x + y
>>> ff = wrapit(verb='launching')(f)
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
>>>
>>> # but can also use it as a "decorator factory"
>>> #wrapit(verb='calling')
... def f(x, y=1):
... return x + y
...
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
The class form could look something like this:
class Wrapit:
def __init__(self, func, verb='calling'):
self.func, self.verb = func, verb
update_wrapper(self, func)
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return self.func(*args, **kwargs)
But how do we get the class to be able to operate in the "decorator factory" mode that the functional form has (implemented by the if func is None: return partial...
How do we integrate that trick in a decorator class?
As was suggested in the comments, you can do this using the __new__ method:
class Wrapit:
def __new__(cls, func=None, *, verb='calling'):
if func is None:
return partial(cls,verb=verb)
self = super().__new__(cls)
self.func, self.verb = func, verb
update_wrapper(self, func)
return self
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return self.func(*args, **kwargs)
The __new__ method is called whenever you try to instantiate a class, and the return value of that method is used as the result of the attempted instantiation -- even if it's not an instance of the class!
I accepted #pppery's answer because... it was the answer. I wanted to extend the answer here by showing how one can get a bit more reuse by coding the logic in a parent class. This requires one to separate #pppery's logic into the __new__ and __init__ methods.
from functools import update_wrapper, partial
class Decorator:
def __new__(cls, func=None, **kwargs):
if func is None:
self = partial(cls, **kwargs)
else:
self = super().__new__(cls)
return update_wrapper(self, func)
def __init__(self, func=None, **kwargs):
self.func = func
for attr_name, attr_val in kwargs.items():
setattr(self, attr_name, attr_val)
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
class Wrapit(Decorator):
def __new__(cls, func=None, *, verb='calling'):
return super().__new__(cls, func, verb=verb)
def __call__(self, *args, **kwargs):
print(f'{self.verb} {self.func.__name__} with {args} and {kwargs}')
return super().__call__(*args, **kwargs)
class AnotherOne(Decorator):
def __new__(cls, func=None, *, postproc=lambda x: x):
return super().__new__(cls, func, postproc=postproc)
def __call__(self, *args, **kwargs):
return self.postproc(super().__call__(*args, **kwargs))
Demo:
>>> f = lambda x, y=1: x + y
>>>
>>> ff = Wrapit(f, verb='launching')
>>> assert ff(10) == 11
launching <lambda> with (10,) and {}
>>> assert signature(ff) == signature(f)
>>>
>>> fff = AnotherOne(postproc=str)(f) # doing it the decorator factory way
>>> assert fff(10) == str(11)
>>> assert signature(fff) == signature(f)
I have an issue with setattr when using it in inheritance with classes and decorators.
#!/usr/bin/env python3
import inspect
def make_class_decorator(function_decorator):
def class_decorator(cls):
for attr_name in inspect.getmembers(cls, inspect.isroutine):
attr_name = attr_name[0]
if str(attr_name).startswith('__') and str(attr_name).endswith('__'): continue
attr_value = getattr(cls, str(attr_name))
setattr(cls, attr_name, function_decorator(cls, attr_value))
return cls
return class_decorator
#make_class_decorator
def auto_debug_logging(cls, called_function):
def wrapped(*args, **kwargs):
print('wrapped')
return called_function(*args, **kwargs)
wrapped.__name__ = called_function.__name__
return wrapped
def xclassmethod(called_function):
def wrapped(*args, **kwargs):
print('wrappedx: ' + called_function.__name__)
return builtins.classmethod(called_function(*args, **kwargs))
return wrapped
class X:
#xclassmethod
def get_i(cls):
cls._inst = cls()
return cls._inst
#auto_debug_logging
class A(X):
pass
#auto_debug_logging
class B(A):
def test(self):
print('test')
B.get_i().test()
Example code shows that all functions are wrapped, and prints "wrapped" before actual call. However, when I call B.get_i().test I'm getting following error:
AttributeError: 'A' object has no attribute 'test'
It seems that setattr somehow sets the original B class to A, as when I comment out that the issue disappears.
I want to use a decorator to do some preparation job and record the status the function have, so I write something like that:
class Decorator:
def __init__(self, func):
self.count = 0
self.func = func
def __call__(self, *args, **kwargs):
self.count += 1 # Simply count the call times
return self.func(self, *args, **kwargs)
class Foo:
def __init__(self):
self.value = 0
#Decorator
def test(self, value):
self.value = value # change the value of instance
print(self.value)
f = Foo()
f.test(1)
print(f.value)
print(f.test.value)
But it's obvious that self in __call__(self, *args, **kwargs) corresponds to instance of Decorator instead of the instance of Foo , which will make f.value unchanged but f.test.value increase .
Is there any way I can pass the instance of Foo to Decorator instead of Decorator itself?
Or is there any way to implement this function much more clear?
As the decorator is only called once and replaces the method for all instance with one instance of the Decorator class. All it does is:
Foo.test = Decorator(Foo.test)
This makes it impossible to detect the instance called. One work-around would be to apply the decorator in the __init__ of Foo by hand:
class Foo:
def __init__(self):
self.value = 0
self.test = Decorator(self.test)
def test(self, value):
self.value = value # change the value of instance
print(self.value)
This way the decorator wraps the instance method, so you do not need to pass self in the __call__ of Decorator:
class Decorator:
def __init__(self, func):
self.count = 0
self.func = func
def __call__(self, *args, **kwargs):
self.count += 1 # Simply count the call times
return self.func(*args, **kwargs)
Now it works and you have to update you test method, as f.test.value no longer exists:
f = Foo()
f.test(1)
print(f.value)
It outputs two times a 1 as expected.
I got this here
import functools
class Decorator(object):
def __init__(self, func):
self.count = 0
self.func = func
def __call__(self, *args, **kwargs):
self.count += 1 # Simply count the call times
return self.func( *args, **kwargs)
def __get__(self, instance, instancetype):
"""Implement the descriptor protocol to make decorating instance
method possible.
"""
# Return a partial function with the first argument is the instance
# of the class decorated.
return functools.partial(self.__call__, instance)
class Foo:
def __init__(self):
self.value = 0
#Decorator
def test(self, value):
self.value = value # change the value of instance
f = Foo()
f.test(3)
print(f.value) # prints 3
g = Foo()
g.test(8)
print(g.value) # prints 8
or
May be this
def preJob(function):
def updateToDo(self, *args, **kwargs):
# do some recording
function(self, *args, **kwargs)
return updateToDo
class Foo(object):
def __init__(self):
self.value = 0
#preJob
def test(self, value):
self.value = value
f = Foo()
f.test(3)
print(f.value) # prints 3
g = Foo()
g.test(8)
print(g.value) # prints 8
class threadSafeGenerator(object):
"""docstring for threadSafeGenerator"""
class SafeGenerator(object):
"""docstring for SafeGenerator"""
def __init__(self, iterable):
self.iterable = iterable
self.lock = Lock()
def __iter__(self):
return self
def __next__(self):
with self.lock:
return next(self.iterable)
def __init__(self, func):
super(threadSafeGenerator, self).__init__()
self.func = func
def __call__(self, *args, **kwargs):
return self.SafeGenerator(self.func(self, *args, **kwargs))
I found using Priyesh Kumar's answer that you can simply pass the self argument from the call method to the function being decorated:
def __call__(self, *args, **kwargs):
return self.SafeGenerator(self.func(self, *args, **kwargs))
hope this helps!
EDIT:
Never mind only works if the function being passed through the decorator does not call class variables defined in the init method
import inspect
import functools
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in inspect.getmembers(cls, inspect.isroutine):
if name.startswith('test'):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
def test_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__, args, kwargs)
res = func(*args, **kwargs)
return res
return wrapper
#for_all_test_methods(test_decorator)
class Potato(object):
def test_method(self):
print('in method')
class Spud(Potato):
def test_derived(self):
print('in derived')
Now if I create a spud instance the test_method which it has inherited remains decorated, but it has an undecorated method test_derived. Unfortunately, if I add the class decorator onto Spud aswell, then his test_method gets decorated twice!
How do I correctly propagate decorators from the parent class onto the children?
You cannot avoid decorating derived classes; you can find subclasses of a class after subclasses have been decorated, but not auto-decorate them. Use a metaclass instead of you need that sort of behaviour.
You can do one of two things:
Detect already-decorated methods; if there is a __wrapped__ attribute you have a wrapper:
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in inspect.getmembers(cls, inspect.isroutine):
if name.startswith('test') and not hasattr(value, '__wrapped__'):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
Limit the class decorator to direct methods only:
def for_all_test_methods(decorator):
def decorate(cls):
for name, value in cls.__dict__.iteritems():
if name.startswith('test') and inspect.isroutine(value)):
setattr(cls, name, test_decorator(getattr(cls, name)))
return cls
return decorate
Here is how you can accomplish this by using a metaclass instead of decorating the class:
import inspect
import functools
def test_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__, args, kwargs)
res = func(*args, **kwargs)
return res
return wrapper
def make_test_deco_type(decorator):
class TestDecoType(type):
def __new__(cls, clsname, bases, dct):
for name, value in dct.items():
if name.startswith('test') and inspect.isroutine(value):
dct[name] = decorator(value)
return super().__new__(cls, clsname, bases, dct)
return TestDecoType
class Potato(object, metaclass=make_test_deco_type(test_decorator)):
def test_method(self):
print('in method')
class Spud(Potato):
def test_derived(self):
print('in derived')
On Python 2.x you would use __metaclass__ = make_test_deco_type(test_decorator) as the first line of the class body instead of having the metaclass=... portion of the class statement. You would also need to replace super() with super(TestDecoType, cls).
I have a decorator declared as a class:
class predicated(object):
def __init__(self, fn):
self.fn = fn
self.fpred = lambda *args, **kwargs: True
def predicate(self, predicate):
self.fpred = predicate
return self
def validate(self, *args, **kwargs):
return self.fpred(*args, **kwargs)
def __call__(self, *args, **kwargs):
if not self.validate(*args, **kwargs):
raise PredicateNotMatchedError("predicate was not matched")
return self.fn(*args, **kwargs)
... and when I use it to wrap a method in a class, calling that method does not seem to set the instance of the object as the first argument. While this behavior is not exactly unexpected, how would I go about getting self to be frozen when the method becomes an instance method?
Simplified example:
class test_decorator(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
class Foo(object):
#test_decorator
def some_method(self):
print(self)
Foo().some_method()
Expected instance of foo, instead get an error saying 0 arguments were passed.
Figured it out - needed to define a __get__ method in order to create a MethodType binding like so:
def __get__(self, obj, objtype=None):
return MethodType(self, obj, objtype)
which creates a MethodType object when invoking the method on an object that freezes the self argument.