开发者

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 calls func with its arguments.
  • another_lower_level_decorator is called, with wrapper as its argument. The function it returns becomes the new value of wrapper.
  • wraps(func) is called to create a wrapper that will apply the name/docstring/etc. of func to whatever function it's called on.
  • The return value of wraps(func), i.e. the produced wrapper function, is passed the current value of wrapper. This, remember, was the return value from another_lower_level_decorator.
  • wraps(func)(wrapper) becomes the new value of wrapper.
  • 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜