1

I have been experimenting with different kinds of decorators in Python and I am finding it difficult to understand Parameterized decorators. A decorator accepts callables and returns callable (callable being a function in my case) In the following code:-

def check_non_negative(index):
    def validator(f):
        def wrap(*args):
            if args[index]<0:
                raise ValueError(
                    'Argument {} must be non negative.'.format(index))
            return f(*args)
        return wrap
    return validator

@check_non_negative(1)
def create_list(value,size):
    return [value]*size


create_list('a',3)

Here I see that check_non_negative is not a decorator according to the definition but behaves like one (During run Validator is the actual decorator). check_non_negative takes an integer and not a callable, yet it behaves like decorator. Can somebody explain why?

3
  • Where is check_non_integer ? Commented Apr 15, 2020 at 16:16
  • Sorry typo, edited with corrections Commented Apr 15, 2020 at 16:18
  • Does stackoverflow.com/questions/739654/… help (scroll to "Passing Arguments to the Decorator")? Commented Apr 15, 2020 at 16:29

2 Answers 2

4

check_non_negative is not technically a decorator by your definition, one might call it a 'decorator factory'. It returns validator, which is a decorator.

Basically whenever you have:

@<expression>
def ...

then <expression> must evaluate to a decorator, i.e. a callable which accepts a single argument that is also a callable.

Sign up to request clarification or add additional context in comments.

1 Comment

aaaah.. Right, it's a decorator factory which is returning validator as a decorator. Thanks!
1

When you pass parameters to a decorator function (i.e. @check_non_negative(1)), the function is invoked with those parameters, and then it returns a decorator that takes and returns a function (like if you'd used @validator with no parameters).

It's easier to understand with type annotations IMO:

import functools
from typing import cast, Callable, List, TypeVar


_Elem = TypeVar('_Elem')
_Func = TypeVar('_Func', bound=Callable)


def check_non_negative(index: int) -> Callable[[_Func], _Func]:
    def validator(f: _Func) -> _Func:
        @functools.wraps(f)
        def wrap(*args, **kwargs):
            if args[index] < 0:
                raise ValueError(
                    'Argument {} must be non negative.'.format(index))
            return f(*args, **kwargs)
        return cast(_Func, wrap)
    return validator


@check_non_negative(1)
def create_list(value: _Elem, size: int) -> List[_Elem]:
    return [value]*size

So check_non_negative takes an int argument, and returns a function (validator) that takes a specific type of function (_Func, which here refers to the type of the decorated function, e.g. create_list) and returns the same type of function.

1 Comment

As Alex said above I agree with the fact that check_non_integer is a decorator factory that is returning a validator for me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.