Need to create a class that will do all things as the "merge" function. In class i will change, process and add new arguments.
def merge(*arg, **kwarg): # get decorator args & kwargs
def func(f):
def tmp(*args, **kwargs): # get function args & kwargs
kwargs.update(kwarg) # merge two dictionaries
return f(*args, **kwargs) # return merged data
return tmp
return func
Usage:
#other_decorator # return *args and **kwarg
#merge(list=['one','two','three']) # need to merge with #other_decorator
def test(*a, **k): # get merged args and kwargs
print 'args:', a
print 'kwargs:', k
I'm not sure I quite get what you're asking. Your implementation works fine, and you won't get around having two levels of indirection if you want to create a parametrized decorator of any kind.
To make merge a class you could do this
class Merge(object):
def __init__(self, **extra_kws):
self.extra_kws = extra_kws
def __call__(self, function):
def _wrapper(*args, **kws):
kws.update(self.extra_kws)
return function(*args, **kws)
return _wrapper
Then you can do this:
#Merge(foo='bar')
def test(*args, **kws):
print *args
print **kws
But you said you want to add change and process new arguments. So presumably you want the decorator itself to be live so you can do:
test.extra_kws['sun'] = 'dock'
After the decorator has been applied. In that case you probably don't want merge to be a class, but you want it to generate a class, so that test is replaced by the modifiable instance:
def merge(**extra_kws):
class _Merge(object):
def __init__(self, function):
self.extra_kws = extra_kws
self.function = function
def __call__(self, *args, **kws):
kws.update(self.extra_kws)
return self.function(*args, **kws)
return _Merge
#merge(foo='bar')
def test(*args, **kws):
print 'args:', args
print 'kws:', kws
test(sun='dock')
test.extra_kws['trog'] = 'cube'
test(sun='dock')
This then allows you to change the keywords on a particular decorated function later.
You could also do the same thing with function arguments without classes:
def merge(**extra_kws):
def _decorator(function):
def _wrapper(*args, **kws):
kws.update(_wrapper.extra_kws)
return function(*args, **kws)
_wrapper.extra_kws = extra_kws
return _wrapper
return _decorator
#merge(foo='bar')
def test(*args, **kws):
print 'kws:', kws
test(sun='dock')
test.extra_kws['trog'] = 'cube'
test(sun='dock')
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)
I currently have a decorator that wraps a function into a class.
(We are currently using this weird, custom async framework where each async call is defined as a class with a ton of boilerplate code. My idea was to just decorate functions and then return the appropriate class.)
This decorator works fine on functions outside of classes. However, when using it with methods, the self argument is no longer implicitly passed, and I'm not sure why.
Here is the best example I could put together
from __future__ import print_function
import functools
def test_wrap(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
func(*args, **kwargs)
return wrapper
def test_class_wrap(func):
"""Return a Command object for use with the custom framework we are using."""
#functools.wraps(func, assigned=('__name__', '__module__'), updated=())
class Command(object):
def __init__(self, *args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
func(*args, **kwargs)
return Command
class MyObject(object):
def __init__(self):
self.value = 100
#test_wrap
def foo(self):
print(self.value)
#test_class_wrap
def bar(self):
print(self.value)
if __name__ == '__main__':
obj = MyObject()
obj.foo()
print()
obj.bar(obj) # works
# obj.bar() # TypeError: bar() takes exactly 1 argument (0 given)
# Why is self implicitly passed as an argument like with outher methods?
# Output
# Args: (<__main__.MyObject object at 0x7fe2bf9bb590>,)
# Kwargs: {}
# 100
# Args: (<__main__.MyObject object at 0x7fe2bf9bb590>,)
# Kwargs: {}
# 100
test_class_wrap does nothing, just returning a class so __init__ isn't called. Try to wrap the class with a function passing args and kwargs:
def test_class_wrap(func):
"""Return a Command object for use with the custom framework we are using."""
#functools.wraps(func, assigned=('__name__', '__module__'), updated=())
def wrapper(*args, **kwargs):
class Command(object):
def __init__(self, *args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
func(*args, **kwargs)
return Command(*args, **kwargs)
return wrapper
...
if __name__ == '__main__':
obj = MyObject()
obj.foo()
print()
obj.bar()
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
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
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?