How do class decorators for methods in classes work? Here is a sample of what I've done through some experimenting:
from functools import wraps
class PrintLog(object):
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog()
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
And this works perfectly fine. However, I was under the impression that decorators do not need to be called with (), but when I remove the brackets from #PrintLog(), I get this error:
def baz(self) -> None:
TypeError: PrintLog() takes no arguments
Is there something I am missing/do not understand? I've also tried passing in a throwaway arg with __init__(), and it works.
class PrintLog(object):
def __init__(self, useless):
print(useless)
def __call__(self, func):
#wraps(func)
def wrapped(*args):
print('I am a log')
return func(*args)
return wrapped
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog("useless arg that I'm passing to __init__")
def baz(self) -> None:
print('inside baz')
Again, this works, but I don't want to pass any argument to the decorator.
tl;dr: This question in python 3.x.
Help appreciated!
Class decorators accept the function as a subject within the __init__ method (hence the log message), so your decorator code should look like:
class PrintLog(object):
def __init__(self, function):
self.function = function
def __call__(self):
#wraps(self.function)
def wrapped(*args):
print('I am a log')
return self.function(*args)
return wrapped
Sorry if this doesn’t work, I’m answering on my mobile device.
EDIT:
Okay so this is probably not what you want, but this is the way to do it:
from functools import update_wrapper, partial, wraps
class PrintLog(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):
#wraps(self.func)
def wrapped(*args):
print('I am a log')
return self.func(*args)
return wrapped(obj, *args)
class foo(object):
def __init__(self, rs: str) -> None:
self.ter = rs
#PrintLog
def baz(self) -> None:
print('inside baz')
bar = foo('2')
print('running bar.baz()')
bar.baz()
The decorator has to have the __get__ method defined because you're applying the decorator to an instance method. How would a descriptor have the context of the foo instance?
Ref: Decorating Python class methods - how do I pass the instance to the decorator?
There is a big picture you're missing.
#decorator
def foo(...):
function_definition
is almost identical (except for some internal mangling) to
temp = foo
foo = decorator(temp)
It doesn't matter what the decorator is, as long as it can act like a function.
Your example is equivalent to:
baz = PrintLog("useless thing")(<saved defn of baz>)
Since PrintLog is a class, PrintLog(...) creates an instance of PrintLog. That instance has a __call__ method, so it can act like a function.
Some decorators are designed to take arguments. Some decorators are designed not to take arguments. Some, like #lru_cache, are pieces of Python magic which look to see if the "argument" is a function (so the decorator is being used directly) or a number/None, so that it returns a function that then becomes the decorator.
Related
I've implemented decorator that can receive extra arguments and want to use it with class methods. I want to pass #property as decorator argument, but instead of #property result I got this:
<property object at 0x7f50f5195230>
This is my decorator:
class Decorator(object):
def __init__(self, some_arg):
self.func = None
self.some_arg = some_arg
def __get__(self, instance, owner):
import functools
return functools.partial(self.__call__, instance)
def __call__(self, func):
self.func = func
def wrapper(*args, **kwargs):
return self._process_sync(*args, **kwargs)
return wrapper
def _process_sync(self, *args, **kwargs):
try:
print(self.some_arg)
return self.func(*args, **kwargs)
except Exception as e:
print(e)
return None
My test class:
class Test(object):
#property
def some_data(self):
return {'key': 'value'}
#Decorator(some_data)
def some_method(self):
print('method output')
return None
Usage:
test = Test()
test.some_method()
Two questions:
How to correctly pass property to receive #property result instead of <property object at 0x7f50f5195230>
Does it possible to pass class properties/methods to the decorator if they are below in code?
A property object is a descriptor. To get a value out of it, you need to call its __get__ method with an appropriate instance. Figuring out when to do that in your current code is not easy, since your Decorator object has a bunch of different roles. It's both a decorator factory (getting initialized with an argument in the #Decorator(x) line), and the decorator itself (getting called with the function to be decorated). You've given it a __get__ method, but I don't expect that to ever get used, since the instance of Decorator never gets assigned to a class variable (only the wrapper function that gets returned from __call__).
Anyway, here's a modified version where the Decorator handles almost all parts of the descriptor protocol itself:
class Decorator:
def __init__(self, arg):
self.arg = arg # this might be a descriptor, like a property or unbound method
def __call__(self, func):
self.func = func
return self # we still want to be the descriptor in the class
def __get__(self, instance, owner):
try:
arg = self.arg.__get__(instance, owner) # try to bind the arg to the instance
except AttributeError: # if it doesn't work, self.arg is not a descriptor, that's OK
arg = self.arg
def wrapper(*args, **kwargs): # this is our version of a bound method object
print(arg) # do something with the bound arg here
return self.func.__get__(instance, owner)(*args, **kwargs)
return wrapper
Consider this decorator:
from functools import wraps
def non_null(func):
#wraps(func)
def wrapper(*args, **kwargs):
print("Test")
assert all(not a is None for a in args)
return func(*args, *kwargs)
return wrapper
Around a property setter of a class function:
#non_null
#foo.setter
def foo(self, argument):
print("Setting")
self._a = argument
If the non_null decorator is used, neither the function nor decorator gets called.
It looks like the #foo.setter have to be the outmost decorator:
#foo.setter
#non_null
def foo(self, argument):
print("Setting")
self._a = argument
I could find no reference for that in the documentation, but examining the content of a class makes clear that if the property decorator in not the outmost one, you only get a simple method instead of a property.
The following minimal example of a decorator on a member function:
def wrap_function(func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
return wrapper
class Foo:
#wrap_function
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
outputs:
(<__main__.Foo object at 0x7fb294939898>, 'hi')
{}
So self is one of the args.
However when using a wrapper class:
class WrappedFunction:
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
print(args)
print(kwargs)
def wrap_function(func):
return WrappedFunction(func)
class Foo:
#wrap_function
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
the output is:
('hi',)
{}
So the self, that references the Foo object, is not accessible in the body of __call__ of the WrappedFunction object.
How can I make it accessible there?
You're losing the reference to your bounded instance by wrapping the function logic (but not the instance) and redirecting it to a class instance - at that point, the class instance's own self applies instead of the wrapped instance method as it gets lost in the intermediary decorator (wrap_function()).
You either have to wrap the call to the wrapped function and pass *args/**kwargs to it, or just make a proper wrapper class instead of adding an intermediary wrapper:
class WrappedFunction(object):
def __call__(self, func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
# NOTE: `WrappedFunction` instance is available in `self`
return wrapper
class Foo:
#WrappedFunction() # wrap directly, without an intermediary
def mem_fun(self, msg):
pass
foo = Foo()
foo.mem_fun('hi')
# (<__main__.Foo object at 0x000001A2216CDBA8>, 'hi')
# {}
Sadly, but this might be the only solution as you need it in the __call__ function.
Would suggest checking this out: What is the difference between __init__ and __call__ in Python?
def wrap_function(func):
def wrapper(*args, **kwargs):
x = WrappedFunction(func)
x(*args, **kwargs)
return wrapper
I have a method decorator like this.
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, f):
self.f = f
self.msg = msg
def __get__(self, instance, _):
def wrapper(test):
print(self.msg)
print(instance.start)
self.f(instance, test)
return self.f
return wrapper
#Decorator
def p1(self, sent):
print(sent)
c = MyClass()
c.p1('test')
This works fine. However, If I want to pass an argument to the decorator, the method is no longer passed as an argument, and I get this error:
TypeError: init() missing 1 required positional argument: 'f'
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, f, msg):
self.f = f
self.msg = msg
def __get__(self, instance, _):
def wrapper(test):
print(self.msg)
print(instance.start)
self.f(instance, test)
return self.f
return wrapper
#Decorator(msg='p1')
def p1(self, sent):
print(sent)
#Decorator(msg='p2')
def p2(self, sent):
print(sent)
How do I pass an argument to the decorator class, and why is it overriding the method?
The descriptor protocol doesn't serve much of a purpose here. You can simply pass the function itself to __call__ and return the wrapper function without losing access to the instance:
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, msg):
self.msg = msg
def __call__(self, f):
def wrapper(instance, *args, **kwargs):
print(self.msg)
# access any other instance attributes
return f(instance, *args, **kwargs)
return wrapper
#Decorator(msg='p1')
def p1(self, sent):
print(sent)
>>> c = MyClass()
>>> c.p1('test')
p1
test
A decorator will be called.
In your case you receive the function as a parameter in the __call__ method
class MyClass:
def __init__(self):
self.start = 0
class Decorator:
def __init__(self, msg):
self.msg = msg
def __call__(self, f):
self.f = f
return self
def __get__(self, instance, _):
def wrapper(test):
print(self.msg)
self.f(instance, test)
return self.f
return wrapper
#Decorator(msg='p1')
def p1(self, sent):
print(sent)
#Decorator(msg='p2')
def p2(self, sent):
print(sent)
Your first example works because calling the Class creates an instance and the function is the parameter.
But in your second example you call the Class manually to set the msg parameter, so you the decoration process calls what's left, i.e.: the instance and that goes to the __call__ method.
When you call a decorator with arguments, the function you call isn't actually working as a decorator itself. Rather, it's a decorator factory (a function or other callable that returns something that will act as the decorator). Usually you solve this by adding an extra level of nested functions. Since you're defining your decorator with a class, that's a bit awkward to do directly (though you probably could make it work). But there doesn't really seem to be any need for your decorator to be a class, as long as you handle self in the wrapper function (it will be the instance of MyClass now, rather than an instance of a Decorator class):
class MyClass:
def __init__(self):
self.start = 0
def decorator_factory(msg):
def decorator(f):
def wrapper(self, test): # you might want to replace test with *args and **kwargs
print(msg)
print(self.start)
return f(self, test)
return wrapper
return decorator
#decorator_factory(msg='p1')
def p1(self, sent):
print(sent)
#decorator_factory(msg='p2')
def p2(self, sent):
print(sent)
I named the decorator factory the way I did to be explicit about the different levels of nested functions, but you should of course use something that's actually meaningful for your use case as the top level name. You might also want to move it out of the class namespace, since it will be available to call on all instances of MyClass (with possibly silly results, since it's not intended to be a method).
I want to construct classes for use as decorators with the following principles intact:
It should be possible to stack multiple such class decorators on top off 1 function.
The resulting function name pointer should be indistinguishable from the same function without a decorator, save maybe for just which type/class it is.
Ordering off the decorators should not be relevant unless actually mandated by the decorators. Ie. independent decorators could be applied in any order.
This is for a Django project, and the specific case I am working on now the method needs 2 decorators, and to appear as a normal python function:
#AccessCheck
#AutoTemplate
def view(request, item_id) {}
#AutoTemplate changes the function so that instead of returning a HttpResponse, it just returns a dictionary for use in the context. A RequestContext is used, and the template name is inferred from the method name and module.
#AccessCheck adds additional checks on the user based on the item_id.
I am guessing it's just to get the constructor right and copy the appropriate attributes, but which attributes are these?
The following decorator won't work as I describe:
class NullDecl (object):
def __init__ (self, func):
self.func = func
def __call__ (self, * args):
return self.func (*args)
As demonstrated by the following code:
#NullDecl
#NullDecl
def decorated():
pass
def pure():
pass
# results in set(['func_closure', 'func_dict', '__get__', 'func_name',
# 'func_defaults', '__name__', 'func_code', 'func_doc', 'func_globals'])
print set(dir(pure)) - set(dir(decorated));
Additionally, try and add "print func.name" in the NullDecl constructor, and it will work for the first decorator, but not the second - as name will be missing.
Refined eduffy's answer a bit, and it seems to work pretty well:
class NullDecl (object):
def __init__ (self, func):
self.func = func
for n in set(dir(func)) - set(dir(self)):
setattr(self, n, getattr(func, n))
def __call__ (self, * args):
return self.func (*args)
def __repr__(self):
return self.func
A do-nothing decorator class would look like this:
class NullDecl (object):
def __init__ (self, func):
self.func = func
for name in set(dir(func)) - set(dir(self)):
setattr(self, name, getattr(func, name))
def __call__ (self, *args):
return self.func (*args)
And then you can apply it normally:
#NullDecl
def myFunc (x,y,z):
return (x+y)/z
The decorator module helps you writing signature-preserving decorators.
And the PythonDecoratorLibrary might provide useful examples for decorators.
To create a decorator that wraps functions in a matter that make them indistinguishable from the original function, use functools.wraps.
Example:
def mydecorator(func):
#functools.wraps(func):
def _mydecorator(*args, **kwargs):
do_something()
try:
return func(*args, **kwargs)
finally:
clean_up()
return _mydecorator
# ... and with parameters
def mydecorator(param1, param2):
def _mydecorator(func):
#functools.wraps(func)
def __mydecorator(*args, **kwargs):
do_something(param1, param2)
try:
return func(*args, **kwargs)
finally:
clean_up()
return __mydecorator
return _mydecorator
(my personal preference is to create decorators using functions, not classes)
The ordering of decorators is as follows:
#d1
#d2
def func():
pass
# is equivalent to
def func():
pass
func = d1(d2(func))