How to pass self into a decorator? - python

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

Related

how do I create a meta-class such that instances of the class are wrapped in `functools.wraps`?

I want to wrap a function name funky_the_function with an object which has a __call__ method defined.
Without using functools.wraps the name of the wrapped function will be lost and so will the docstring.
How do I create a meta-class such that instances of the class are wrapped in functools.wraps?
import functools
class MetaDecorator(type):
def __call__(self, *args):
super().__call__(*args)
# SOMEWHERE INSIDE OF `__call__` WE HAVE:
# obj = functools.wraps(obj)
class Decorator(metaclass=MetaDecorator):
def __init__(f):
assert(callable(f))
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
#Decorator
def funky_the_function(*args, **kwargs):
"""Documentation string"""
print('Called example function')
print(funky_the_function.__name__)
print(funky_the_function.__doc__)
1. without metaclass, without wraps:
If you're looking for a way to fix the name and docstring, it can be easily fix by dynamically adding the __name__ and __doc__ to the instance. There is no need for meta-classes.
class Decorator:
def __init__(self, f):
assert callable(f)
self.__name__ = f.__name__
self.__doc__ = f.__doc__
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
2. with metaclass, without wraps:
Of course you can do this in metaclass as well:
class MetaDecorator(type):
def __call__(self, f):
assert callable(f)
instance = super().__call__(f)
instance.__name__ = f.__name__
instance.__doc__ = f.__doc__
return instance
class Decorator(metaclass=MetaDecorator):
def __init__(self, f):
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
3. with metaclass, with wraps:
from functools import wraps
class MetaDecorator(type):
def __call__(self, f):
assert callable(f)
instance = super().__call__(f)
instance = wraps(f)(instance)
return instance
class Decorator(metaclass=MetaDecorator):
def __init__(self, f):
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)

How to add type annotation to self parameter of the decorator of class method?

So, I have a decorator for a class like this
def something_todo(f):
#functools.wraps(f)
def decorator(self, *args, **kwargs):
# do something
return f(self, *args, **kwargs)
return decorator
I want to have type annotation to self parameter in the decorator. But, writing like this is not working
def something_todo(f):
#functools.wraps(f)
def decorator(self: SomeClass, *args, **kwargs):
# do something
return f(self, *args, **kwargs)
return decorator
class SomeClass:
def __init__(self):
# init here
#something_todo
def some_method(self, *args, **kwargs):
# some process
And it even gets worse if I write the decorator and the class in different scripts. This will likely be happened
ImportError: cannot import name 'SomeClass' from partially initialized module 'someclass' (most likely due to a circular import)
The reason why I did this is for clarity so people know that my decorator is only for that class method. Also, I would easily check all methods or properties of the class in the editor without the need to open the script that contains the class.
EDIT
So this is the recap of the solution:
Let's say I have 2 scripts. One contains the class, and one contains the decorator function. For example,
# inside decorators.py
def something_todo(f):
#functools.wraps(f)
def decorator(self, *args, **kwargs):
# do something
return f(self, *args, **kwargs)
return decorator
and
# inside someclass.py
from decorators import something_todo
class SomeClass:
def __init__(self):
# init here
#something_todo
def some_method(self, *args, **kwargs):
# some process
If I want to add a type annotation to self parameter in the decorator, I couldn't just do this
# inside decorators.py
from someclass import SomeClass
def something_todo(f):
#functools.wraps(f)
def decorator(self: SomeClass, *args, **kwargs):
# do something
return f(self, *args, **kwargs)
return decorator
Because it will raise ImportError when I tried to import the class inside another script. So, to prevent the error, I could just do this
# inside decorators.py
from someclass import *
def something_todo(f):
#functools.wraps(f)
def decorator(self: "SomeClass", *args, **kwargs):
# do something
return f(self, *args, **kwargs)
return decorator
And it works fine.
Two useful tricks:
Forward-declare types as string literals, e.g. self: 'SomeClass'
Use callable protocols for more flexibility in defining callable TypeVars.
import functools
from typing import cast, Any, Callable, Protocol, TypeVar
class SomeMethod(Protocol):
def __call__(
_self,
self: 'SomeClass',
*args: Any,
**kwargs: Any
) -> Any: ...
_SomeMethod = TypeVar("_SomeMethod", bound=SomeMethod)
def something_todo(f: _SomeMethod) -> _SomeMethod:
#functools.wraps(f)
def wrapper(self: SomeClass, *args, **kwargs):
# do something
return f(self, *args, **kwargs)
return cast(_SomeMethod, wrapper)
class SomeClass:
def __init__(self):
# init here
pass
#something_todo
def some_method(self, *args, **kwargs):
# some process
pass
#something_todo
def foo():
print('error: Value of type variable "_SomeMethod" of "something_todo" cannot be "Callable[[], None]"')
you did not return the decorator function. try adding the line return decorator. there is nothing wrong with the implementation if i understand your question correctly.
from functools import wraps
def something_todo(f):
#wraps(f)
def decorator(self, *args, **kwargs):
# do something
print("test decorator")
return f(self, *args, **kwargs)
return decorator
class SomeClass:
def __init__(self):
pass
#something_todo
def some_method(self, *args, **kwargs):
print("test some method")
SomeClass().some_method()
prints :
test decorator
test some method

How to use a class method to decorate other class methods?

When designing a class, I find that in the class methods, there are repeated steps are called each time when invoke the class method. For example:
class Queue(object):
def __init__(self):
self.connection = Connection()
def create(self, name):
self.probe = self.connection.plug()
self.probe.create(name)
self.probe.unplug()
def delete(self, name):
self.probe = self.connection.plug()
self.probe.delete(name)
self.probe.unplug()
And there are many methods require the similar steps to 'plug' and 'unplug' the 'probe'. In this design we need to 'plug' and 'unplug' the 'probe' each time we perform the actions.
Thus I am thinking about the wrap those functions by decorator to make the code looking less repeated.
class Queue(object):
def __init__(self):
self.connection = Connection()
def _with_plug(self, fn):
def wrapper(*args, **kwargs):
self.probe = self.connection.plug()
fn(*args, **kwargs)
self.probe.unplug()
#_with_plug
def create(self, name):
self.probe.create(name)
#_with_plug
def delete(self, name):
self.probe.delete(name)
But this strategy is not working. How could I use a method in the class to decorate other methods to perform such actions before and after calling a method?
Seems like a bit of muddled arguments to me:
file deco.py, say
def _with_plug(fn): # decorator takes exactly one argument, the function to wrap
print("wrapping", fn.__name__)
def wrapper(self, *args, **kwds):
print("wrapper called")
self.probe = [self.connection, ".plug()"]
fn(self, *args, **kwds)
self.probe.append(".unplug()")
return wrapper # decorator must return the wrapped function
class Queue(object):
def __init__(self):
self.connection = "Connection()"
#_with_plug
def create(self, name):
self.probe.append("create(name)")
#_with_plug
def delete(self, name):
self.probe.append("delete(name)")
Check:
>>> import deco
wrapping create
wrapping delete
>>> q = deco.Queue()
>>> q.create("name")
wrapper called
>>> q.probe
['Connection()', '.plug()', 'create(name)', '.unplug()']
Observe that the decorator function is called at definition time of the to-be-wrapped function, i.e. before the class definition is completed and long before the first instance is created. Therefore you can't reference self in the way you tried.
You should define your decorator function outside of the class body and your decorator function should return the wrapped function in order for it to work. Something like:
def _with_plug(fn):
def wrapper(self, *args, **kwargs):
self.probe = self.connection.plug()
fn(self, *args, **kwargs)
self.probe.unplug()
return wrapper
class Queue(object):
def __init__(self):
self.connection = Connection()
#_with_plug
def create(self, name):
self.probe.create(name)
#_with_plug
def delete(self, name):
self.probe.delete(name)

Using a decorator that returns a class on a method?

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()

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