Decorating class method at runtime - python

I want to decorate a class method, but at runtime (by that I mean that the decorator does not need to be specified before the method with the # notation)
See this example of a standard method decorator:
def method_decorator(func):
def decorated(self, *args, **kwargs):
print('decorator:', args, kwargs)
return func(self,*args, **kwargs)
return decorated
class Standard():
# method_decorator
def decorated(self, *args, **kwargs):
print('decorated: ', args, kwargs)
s = Standard()
s.decorated(1,2)
Result:
decorator: (1, 2) {}
decorated: (1, 2) {}
So I tried different ways to do it at runtime:
class RunTime():
def set_decorator_1(self, decorator):
self.decorated = decorator(self.decorated)
def set_decorator_2(self, decorator):
RunTime.decorated = decorator(RunTime.decorated)
def set_decorator_3(self, decorator):
self.decorated = decorator(RunTime.decorated)
def set_decorator_4(self, decorator):
setattr(self, 'decorated', decorator(RunTime.decorated))
def set_decorator_5(self, decorator):
setattr(self, 'decorated', decorator(self.decorated))
def decorated(self, *args, **kwargs):
print('decorated: ', args, kwargs)
r = RunTime()
r.set_decorator_*(method_decorator)
r.decorated(1,2)
And here are the outputs:
The decorator is not properly decorated:
decorator: (2,) {}
decorated: (1, 2) {}
Works as expected, but when set_decorator is called, all RunTime instances are also decorated, which I want to avoid, because I only want to decorate the method of a single instance.
Bad decoration
decorator: (2,) {}
decorated: (2,) {}
same as 3
same as 1
I also tries another decorator, which works well (with set_decorator_1) but does not allow me to access self in it:
def method_decorator_runtime(func):
def decorated( *args, **kwargs):
print('decorator: ', args, kwargs)
return func( *args, **kwargs)
return decorated
Does anyone know a proper way of decorating a method at run time, with ability to access self in the decorator ?

The closest solution I have found for your question is to use a decorator with an argument where you can pass the instance of the object that has its method decorated.
def method_decorator(self_eventually):
def decorator(func):
def decorated(*args, **kwargs):
print('decorator:', repr(self_eventually), args, kwargs)
return func(*args, **kwargs)
return decorated
return decorator
class RunTime():
def set_decorator(self, decorator):
self.decorated = decorator(self)(self.decorated)
def decorated(self, *args, **kwargs):
print('decorated:', repr(self), args, kwargs)
r = RunTime()
r.set_decorator(method_decorator)
r.decorated(1, 2)

Related

Decorators as classes

Trying to rewrite a decorator as a Class isn't working as expected. My actual decorator is:
def check(callback):
def decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
cb_result = callback()
return result
return wrapper
return decorator
My approach to class format is
class Check(object):
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
def __call__(self, *call_args, **call_kwargs):
function = call_args[0]
return self.__param__call__(function)
def __param__call__(self, function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
cb_result = callback()
return result
return wrapper
I expect to use the decorator as in:
#Check(callback=a_function_callback)
def my_function():
...
What is my mistake while rewriting it as a class, I'm also always trying to keep backwards compatibility (aka Python 2.7 compliance).
You should accept callback as a parameter in your Check class' __init__ method, so that your wrapper function can actually reference it as a callback function:
class Check(object):
def __init__(self, callback):
self.callback = callback
def __call__(self, func):
return self.__param__call__(func)
def __param__call__(self, func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
cb_result = self.callback()
return result
return wrapper

Conditional Decorator Implementation

I want to have conditional decorator. Following is my code:-
def int_decorator(func): # checks whether the args passed is of type int.
def wrapper(*args, **kwargs):
a, b = args
if not (isinstance(a, int) and isinstance(b, int)):
return
return func(*args, **kwargs)
return wrapper
decorator_mapping = {
'int': int_decorator
}
class conditional_decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
decorator = decorator_mapping.get(kwargs.get('decorator'))
if not decorator:
# Return the function unchanged, not decorated.
return self.func(*args, **kwargs)
return decorator(self.func(*args, **kwargs))
#conditional_decorator
def func(a, b, *args, **kwargs):
return(1)
print(func(1, 2, decorator='int'))
What I want is if there exist any decorator in decorator_mapping that matches the value passed in the func, then apply the same decorator on the function, else don't apply any decorator.
The above code works well, when there isn;t any decorator, but fails when a decorator is found. It print's the reference of the function.
Where am I wrong?
Decorator accepts a function, returns a function. This line:
return decorator(self.func(*args, **kwargs))
should be:
return decorator(self.func)(*args, **kwargs)
def __call__(self, *args, **kwargs):
decorator = decorator_mapping.get(kwargs.get('decorator'))
if not decorator:
# Return the function unchanged, not decorated.
return self.func(*args, **kwargs)
return decorator(self.func)(*args, **kwargs)
You want to decorate the function, then call the decorated version of it.

How to pass self into a decorator?

How do I pass self.key below into the decorator?
class CacheMix(object):
def __init__(self, *args, **kwargs):
super(CacheMix, self).__init__(*args, **kwargs)
key_func = Constructor(
memoize_for_request=True,
params={'updated_at': self.key}
)
#cache_response(key_func=key_func)
def list(self, *args, **kwargs):
pass
class ListView(CacheMix, generics.ListCreateAPIView):
key = 'test_key'
I get the error:
'self' is not defined
Here's an example of doing it with a class decorator as I tried to describe to you in the comments. I filled-in a few undefined references in your question and used a super-simplified version of your cache_response function decorator, but hopefully this will convey the idea concretely enough for you to be able adapt it to your real code.
import inspect
import types
class Constructor(object):
def __init__(self, memoize_for_request=True, params=None):
self.memoize_for_request = memoize_for_request
self.params = params
def __call__(self):
def key_func():
print('key_func called with params:')
for k, v in self.params.items():
print(' {}: {!r}'.format(k, v))
key_func()
def cache_response(key_func):
def decorator(fn):
def decorated(*args, **kwargs):
key_func()
fn(*args, **kwargs)
return decorated
return decorator
def example_class_decorator(cls):
key_func = Constructor( # define key_func here using cls.key
memoize_for_request=True,
params={'updated_at': cls.key} # use decorated class's attribute
)
# create and apply cache_response decorator to marked methods
# (in Python 3 use types.FunctionType instead of types.UnboundMethodType)
decorator = cache_response(key_func)
for name, fn in inspect.getmembers(cls):
if isinstance(fn, types.UnboundMethodType) and hasattr(fn, 'marked'):
setattr(cls, name, decorator(fn))
return cls
def decorate_me(fn):
setattr(fn, 'marked', 1)
return fn
class CacheMix(object):
def __init__(self, *args, **kwargs):
super(CacheMix, self).__init__(*args, **kwargs)
#decorate_me
def list(self, *args, **kwargs):
classname = self.__class__.__name__
print('list() method of {} object called'.format(classname))
#example_class_decorator
class ListView(CacheMix):
key = 'test_key'
listview = ListView()
listview.list()
Output:
key_func called with params:
updated_at: 'test_key'
list() method of ListView object called
I just found out that if you write the decorator function like so:
def decorator(the_func):
#wraps(the_func)
def wrapper(*args, **kwargs):
the_func(*args, **kwargs)
return wrapper
and decorate any method which takes self as an argument, self will appear in args. Therefore you can do this:
from functools import wraps
class myClass:
def __init__(self):
self.myValue = "Hello"
def decorator(the_func):
#wraps(the_func)
def wrapper(*args, **kwargs):
print(args[0].myValue)
the_func(*args, **kwargs)
return wrapper
#decorator
def myFunction(self):
print("World")
Call it like you normally would
foo = myClass()
foo.myFunction()
and you should get
Hello
World

Decorator that works on both classmethods and instance methods

I have two decorators, defined as follows, both of which do the exact same thing:
# ONLY WORKS FOR CLASSMETHODS
def paginated_class_method(default_page_size=25):
def wrap(func):
#functools.wraps(func)
def inner(cls, page=1, page_size=default_page_size, *args, **kwargs):
objects = func(cls=cls, *args, **kwargs)
return _paginate(objects, page, page_size)
return inner
return wrap
# ONLY WORKS FOR INSTANCEMETHODS
def paginated_instance_method(default_page_size=25):
def wrap(func):
#functools.wraps(func)
def inner(self, page=1, page_size=default_page_size, *args, **kwargs):
objects = func(self=self, *args, **kwargs)
return _paginate(objects, page, page_size)
return inner
return wrap
The reason I have two is because for class methods I need to pass in the first arg as cls=cls, and for instance methods I need to pass in self=self. But this is obviously not ideal. Does anyone know a way to structure a decorator that would work for instance methods and class methods?
Just pass in cls or self as the first positional argument, there is no need to pass them is as keyword arguments:
def paginated_class_method(default_page_size=25):
def wrap(func):
#functools.wraps(func)
def inner(self_or_cls, page=1, page_size=default_page_size, *args, **kwargs):
objects = func(self_or_cls, *args, **kwargs)
return _paginate(objects, page, page_size)
return inner
return wrap

Using the same decorator (with arguments) with functions and methods

I have been trying to create a decorator that can be used with both functions and methods in python. This on it's own is not that hard, but when creating a decorator that takes arguments, it seems to be.
class methods(object):
def __init__(self, *_methods):
self.methods = _methods
def __call__(self, func):
def inner(request, *args, **kwargs):
print request
return func(request, *args, **kwargs)
return inner
def __get__(self, obj, type=None):
if obj is None:
return self
new_func = self.func.__get__(obj, type)
return self.__class__(new_func)
The above code wraps the function/method correctly, but in the case of a method, the request argument is the instance it is operating on, not the first non-self argument.
Is there a way to tell if the decorator is being applied to a function instead of a method, and deal accordingly?
To expand on the __get__ approach. This can be generalized into a decorator decorator.
class _MethodDecoratorAdaptor(object):
def __init__(self, decorator, func):
self.decorator = decorator
self.func = func
def __call__(self, *args, **kwargs):
return self.decorator(self.func)(*args, **kwargs)
def __get__(self, instance, owner):
return self.decorator(self.func.__get__(instance, owner))
def auto_adapt_to_methods(decorator):
"""Allows you to use the same decorator on methods and functions,
hiding the self argument from the decorator."""
def adapt(func):
return _MethodDecoratorAdaptor(decorator, func)
return adapt
In this way you can just make your decorator automatically adapt to the conditions it is used in.
def allowed(*allowed_methods):
#auto_adapt_to_methods
def wrapper(func):
def wrapped(request):
if request not in allowed_methods:
raise ValueError("Invalid method %s" % request)
return func(request)
return wrapped
return wrapper
Notice that the wrapper function is called on all function calls, so don't do anything expensive there.
Usage of the decorator:
class Foo(object):
#allowed('GET', 'POST')
def do(self, request):
print "Request %s on %s" % (request, self)
#allowed('GET')
def do(request):
print "Plain request %s" % request
Foo().do('GET') # Works
Foo().do('POST') # Raises
The decorator is always applied to a function object -- have the decorator print the type of its argument and you'll be able to confirm that; and it should generally return a function object, too (which is already a decorator with the proper __get__!-) although there are exceptions to the latter.
I.e, in the code:
class X(object):
#deco
def f(self): pass
deco(f) is called within the class body, and, while you're still there, f is a function, not an instance of a method type. (The method is manufactured and returned in f's __get__ when later f is accessed as an attribute of X or an instance thereof).
Maybe you can better explain one toy use you'd want for your decorator, so we can be of more help...?
Edit: this goes for decorators with arguments, too, i.e.
class X(object):
#deco(23)
def f(self): pass
then it's deco(23)(f) that's called in the class body, f is still a function object when passed as the argument to whatever callable deco(23) returns, and that callable should still return a function object (generally -- with exceptions;-).
Since you're already defining a __get__ to use your decorator on the Bound Method, you could pass a flag telling it if it's being used on a method or function.
class methods(object):
def __init__(self, *_methods, called_on_method=False):
self.methods = _methods
self.called_on_method
def __call__(self, func):
if self.called_on_method:
def inner(self, request, *args, **kwargs):
print request
return func(request, *args, **kwargs)
else:
def inner(request, *args, **kwargs):
print request
return func(request, *args, **kwargs)
return inner
def __get__(self, obj, type=None):
if obj is None:
return self
new_func = self.func.__get__(obj, type)
return self.__class__(new_func, called_on_method=True)
Here is a general way I found to detect whether a decorated callable is a function or method:
import functools
class decorator(object):
def __init__(self, func):
self._func = func
self._obj = None
self._wrapped = None
def __call__(self, *args, **kwargs):
if not self._wrapped:
if self._obj:
self._wrapped = self._wrap_method(self._func)
self._wrapped = functools.partial(self._wrapped, self._obj)
else:
self._wrapped = self._wrap_function(self._func)
return self._wrapped(*args, **kwargs)
def __get__(self, obj, type=None):
self._obj = obj
return self
def _wrap_method(self, method):
#functools.wraps(method)
def inner(self, *args, **kwargs):
print('Method called on {}:'.format(type(self).__name__))
return method(self, *args, **kwargs)
return inner
def _wrap_function(self, function):
#functools.wraps(function)
def inner(*args, **kwargs):
print('Function called:')
return function(*args, **kwargs)
return inner
Example usage:
class Foo(object):
#decorator
def foo(self, foo, bar):
print(foo, bar)
#decorator
def foo(foo, bar):
print(foo, bar)
foo(12, bar=42) # Function called: 12 42
foo(12, 42) # Function called: 12 42
obj = Foo()
obj.foo(12, bar=42) # Method called on Foo: 12 42
obj.foo(12, 42) # Method called on Foo: 12 42
A partial (specific) solution I have come up with relies on exception handling. I am attempting to create a decorator to only allow certain HttpRequest methods, but make it work with both functions that are views, and methods that are views.
So, this class will do what I want:
class methods(object):
def __init__(self, *_methods):
self.methods = _methods
def __call__(self, func):
#wraps(func)
def inner(*args, **kwargs):
try:
if args[0].method in self.methods:
return func(*args, **kwargs)
except AttributeError:
if args[1].method in self.methods:
return func(*args, **kwargs)
return HttpResponseMethodNotAllowed(self.methods)
return inner
Here are the two use cases: decorating a function:
#methods("GET")
def view_func(request, *args, **kwargs):
pass
and decorating methods of a class:
class ViewContainer(object):
# ...
#methods("GET", "PUT")
def object(self, request, pk, *args, **kwargs):
# stuff that needs a reference to self...
pass
Is there a better solution than to use exception handling?

Categories