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.
Related
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)
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
I have code below
#newrelic.agent.data_store_trace('Mysql', '<name>',None)
def get_user(request=None, name=settings.DEFAULT_NAME):
# Some implementation
In the decorator, in place of <name> I want to pass name which is in the decorated function.
Note that I don't want to modify/override the decorator as newrelic updates the packages time-to-time, It would be a problem for us.
Any solution???
Decorators are simply wrapper functions.
Write another wrapper function that wraps newrelic.agent.data_store_trace and allows to pass a name.
Say New Relic defines data_store_trace as:
import functools
def data_store_trace(product, target, operation):
def wraps(fn):
#functools.wraps(fn)
def wrapped(*args, **kwargs):
print('Tracing: ', fn.__name__, args, kwargs)
return fn(*args, **kwargs)
return wrapped
return wraps
Adding another level of indirection will give:
def data_store_trace_with_name(product, operation, target=''):
def wraps(fn):
#functools.wraps(fn)
def wrapped(*args, **kwargs):
return data_store_trace(product, target, operation)(fn)(*args, **kwargs)
return wrapped
return wraps
#data_store_trace_with_name('Mysql', None, '<name>')
def add(x, y):
return x + y
add(5, 6)
In the below code, how would I obtain *args and **kwargs in function f without the need for the wrapper function?
def f(func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
return func(*args,**kwargs)
return wrapper
#f
def write(text):
print(text)
# write = a(write)
write('dog')
Failed attempt 1:
def f(func):
a=func(*args)
k=func(**kwargs)
which causes error:
NameError: global name 'args' is not defined
Failed attempt 2:
def f(func(*args,**kwargs)):
a=func(*args)
k=func(**kwargs)
The wrapper function is necessary, and a standard part of how decorator definitions in Python work.
You can, however, help mask the existence of the wrapper function in tracebacks by using functools.wraps():
import functools
def f(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
This will update the wrapper function to have the name and docstring of the wrapped function.
--
Decorators are nothing more than functions which are passed a function. This code...
def dec(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
#dec
def myfunc(foo, bar):
return foo+bar
is equivalent to this code:
def dec(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def myfunc(foo, bar):
return foo+bar
myfunc = dec(myfunc)
Notice how the thing being passed to dec is a function which hasn't even been called yet - so there aren't any arguments passed at the time when dec is invoked. This is why the wrapper function is involved: it adds a layer which will be called when the original function is invoked which can capture arguments.
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?