Let us do a little more fantasizing. Imagine we want to be able to validate arguments of functions — check whether their values correspond to rules. And we want to do it using decorators that can be applied again. We will implement a couple of these decorators by the end of the lesson.
Using decorators with parameters
But first, we need to digress a little. What happens if the arguments of the function do not pass our verification? We need to show the error. But how do we do it? We will tell you more about working with errors in the subsequent, but for now, we will show you how to provoke an error:
raise ValueError('Value too low!')
# From the traceback, the most recent calls are the last:
# File "<stdin>", line 1, in <module>
# ValueError: Value is too low!
It is the error we will show if the argument value does not pass validation. Now we can start creating decorators. Let us say a function has a numeric argument greater than zero and is not equal to invalid values.
Of course, we could make this sort of special decorators:
@greater_than_zero
@not_bad
def function(arg):
# …
But that is not enough for all instances of such highly specialized decorators. We separate the wrapping of the function and the checks so that the usual predicates can act as the checks. But how does the decorator know about the predicate if it always accepts the wrapped function as a single parameter?
We can use closure. We need a function that will take a predicate function as an argument and return a wrapper function, and then it will also take a function as an argument and return the same function. Now Let us get down to writing this function layer cake:
def checking_that_arg_is(predicate, error_message):
def wrapper(function):
def inner(arg):
if not predicate(arg):
raise ValueError(error_message)
return function(arg)
return inner
return wrapper
The function checking_that_arg_is
takes a predicate and returns wrapper
. Here, wrapper
is now our decorator with inner
inside. The inner
checks the argument using a predicate. If we meet the condition, we call it function
.
Over time you will be able to read and write this sort of code quickly and easily because decorators, including those with parameters, are often seen in Python code. Using a decorator with parameters looks like this:
@checking_that_arg_is(condition, "Invalid value!")
def foo(arg):
# …
At last, we have something to wrap. Now we will write some closures that will act as checks:
def greater_than(value):
def predicate(arg):
return arg > value
return predicate
def in_(*values):