Python - correct order of the application of decorators
I'm decorating a function as such:
def some_abstract_decorator(func):
@another_lower_level_decorator
def wrapper(*args, **kwargs):
# ... details omitted
return func(*args, **kwargs)
return wrapper
This does what you'd expect (applies a low level decorator and then does some more stuff. My problem is that I now want to use functools.wraps
and I don't know where to put it. This is my guess, but I don't know if it'll have unintended consequences.
def some_abstract_decorator(func):
@wraps(func)
@another_lower_level_decorator
def wrapper(*args, **kwargs):
# ... details omitted
return func(*args, *开发者_StackOverflow中文版*kwargs)
return wrapper
(I of course apply wraps
inside of another_lower_level_decorator
as well)
Try it out:
from functools import wraps
def another_lower_level_decorator(func):
@wraps( func )
def wrapped(*args, **kwargs):
return func(*args, **kwargs)
return wrapped
def some_abstract_decorator(func):
@wraps(func)
@another_lower_level_decorator
def wrapper(*args, **kwargs):
# ... details omitted
return func(*args, **kwargs)
return wrapper
@some_abstract_decorator
def test():
""" This is a docstring that should be on the decorated function """
pass
help(test)
Prints:
Help on function test in module __main__:
test(*args, **kwargs)
This is a docstring that should be on the decorated function
As you can see it works! The docstring is there and the name assigned.
But this works just the same:
def some_abstract_decorator(func):
@another_lower_level_decorator
@wraps(func)
def wrapper(*args, **kwargs):
# ... details omitted
return func(*args, **kwargs)
return wrapper
wraps
just fixes the docstrings/names. As long as all the decorators use wraps
, the order in which you apply it doesn't matter
Btw, there is a much cooler decorator library:
from decorator import decorator
@decorator
def another_decorator(func, *args, **kwargs):
return func(*args, **kwargs)
@decorator
@another_decorator
def some_abstract_decorator(func, *args, **kwargs):
# ... details omitted
return func(*args, **kwargs)
@some_abstract_decorator
def test(x):
""" this is a docstring that should be on the decorated function """
pass
That's right. The way this works is
wrapper
is defined. It callsfunc
with its arguments.another_lower_level_decorator
is called, withwrapper
as its argument. The function it returns becomes the new value ofwrapper
.wraps(func)
is called to create a wrapper that will apply the name/docstring/etc. offunc
to whatever function it's called on.- The return value of
wraps(func)
, i.e. the produced wrapper function, is passed the current value ofwrapper
. This, remember, was the return value fromanother_lower_level_decorator
. wraps(func)(wrapper)
becomes the new value ofwrapper
.- That value is returned by
some_abstract_decorator
, making that function suitable for use as a decorator.
Or that's effectively it, anyway. I think in practice wrapper
is only reassigned to once.
Yes, that looks right to me. @another_lower_level_decorator
will return a function, which @wraps
will wrap so that it has the same name as func
.
精彩评论