Using python decorator with or without parentheses

后端 未结 4 1777
心在旅途
心在旅途 2021-02-01 01:04

In Python, what is the difference between using the same decorator with and without parentheses?

For example:

Without parentheses:

@some         


        
相关标签:
4条回答
  • 2021-02-01 01:37

    Some actually working code where you use the arg inside decorator:

    def someDecorator(arg=None):
        def decorator(func):
            def wrapper(*a, **ka):
                if not callable(arg):
                    print (arg)
                    return func(*a, **ka)
                else:
                    return 'xxxxx'
            return wrapper
    
        if callable(arg):
            return decorator(arg) # return 'wrapper'
        else:
            return decorator # ... or 'decorator'
    
    @someDecorator(arg=1)
    def my_func():
        print('aaa')
    
    @someDecorator
    def my_func1():
        print('bbb')
    
    if __name__ == "__main__":
        my_func()
        my_func1()
    

    The output is:

    1
    aaa
    
    0 讨论(0)
  • 2021-02-01 01:47

    If you have a decorator that can be used with or without parameters, you can use the following decorator on your decorator to make it usable with or without parentheses, like this:

    >>> @omittable_parentheses(allow_partial=True)
    ... def multiplier(multiply_by=2):
    ...     def decorator(func):
    ...         def multiplying_wrapper(*args, **kwargs):
    ...             return multiply_by * func(*args, **kwargs)
    ...         return multiplying_wrapper
    ...     return decorator
    ...
    >>> @multiplier
    ... def no_parentheses():
    ...     return 2
    ...
    >>> no_parentheses()
    4
    >>> @multiplier()
    ... def parentheses():
    ...     return 2
    ...
    >>> parentheses()
    4
    >>> @multiplier(3)
    ... def parameter():
    ...     return 2
    ...
    >>> parameter()
    6
    

    If allow_partial=True is given, the resulting decorator will also work with with partial:

    >>> from functools import partial
    >>> multiply_by_3 = partial(multiplier, multiply_by=3)
    >>>
    >>> @multiply_by_3
    ... def partial_no_parentheses():
    ...     return 2
    ...
    >>> partial_no_parentheses()
    6
    >>> @multiply_by_3()
    ... def partial_parentheses():
    ...     return 2
    ...
    >>> partial_parentheses()
    6
    

    Decorator code:

    from functools import wraps
    
    def omittable_parentheses(maybe_decorator=None, /, allow_partial=False):
        """A decorator for decorators that allows them to be used without parentheses"""
        def decorator(func):
            @wraps(decorator)
            def wrapper(*args, **kwargs):
                if len(args) == 1 and callable(args[0]):
                    if allow_partial:
                        return func(**kwargs)(args[0])
                    elif not kwargs:
                        return func()(args[0])
                return func(*args, **kwargs)
            return wrapper
        if maybe_decorator is None:
            return decorator
        else:
            return decorator(maybe_decorator)
    

    As a bonus, this decorator decorator can be itself used with or without parentheses!

    0 讨论(0)
  • 2021-02-01 01:48

    some_decorator in the first code snippet is a regular decorator:

    @some_decorator
    def some_method():
        pass
    

    is equivalent to

    some_method = some_decorator(some_method)
    

    On the other hand, some_decorator in the second code snippet is a callable that returns a decorator:

    @some_decorator()
    def some_method():
        pass
    

    is equivalent to

    some_method = some_decorator()(some_method)
    

    As pointed out by Duncan in comments, some decorators are designed to work both ways. Here's a pretty basic implementation of such decorator:

    def some_decorator(arg=None):
        def decorator(func):
            def wrapper(*a, **ka):
                return func(*a, **ka)
            return wrapper
    
        if callable(arg):
            return decorator(arg) # return 'wrapper'
        else:
            return decorator # ... or 'decorator'
    

    pytest.fixture is a more complex example.

    0 讨论(0)
  • 2021-02-01 01:48

    Briefly speaking, decorators allow adding rich features to groups of functions and classes without modifying them at all.

    The key to understand the difference between @some_decorator and @some_decorator() is that the former is decorator, while the latter is a function (or callable) that returns a decorator.

    I believe that seeing an implementation of each case facilitates understanding the difference:

    @some_decorator

    def some_decorator(func):
        def wrapper(func):
            return func(*args, **kwargs)
        return wrapper
    

    Application:

    @some_decorator
    def some_method():
        pass
    

    Equivalence:

    some_method = some_decorator(some_method)
    

    @some_decorator()

    def some_decorator():
        def decorator(func):
            def wrapper(*args, **kwargs):
                return func(*args, **kwargs)
            return wrapper
        return decorator
    

    Application:

    @some_decorator()
    def some_method():
        pass
    

    Equivalence:

    some_method = some_decorator()(some_method)
    

    Notice that now it is easier to see that @some_decorator() is a function returning a decorator while some_decorator is just a decorator. Keep in mind that some decorators are written to work both ways.

    So now you might be wondering why we have these two cases when the former version seems simpler. The answer is that if you want to pass arguments to a decorator, using @some_decorator() will allow you to do this. Let's see some code in action:

    def some_decorator(arg1, arg2):
        def decorator(func):
            def wrapper(*args, **kwargs):
                print(arg1)
                print(arg2)
                return func(*args, **kwargs)
            return wrapper
        return decorator
    

    Application:

    @some_decorator('hello', 'bye')
    def some_method():
        pass
    

    Equivalence:

    some_method = some_decorator('hello', 'bye')(some_method)
    

    Note: I think that it is worth to mention that a decorator can be implemented as a function or as a class. Check this for more information.

    0 讨论(0)
提交回复
热议问题