Tutorial
To fully appreciate the power and flexibility of composable predicates, we provide a number of examples in this tutorial.
Tutorial 1
Task: define a predicate expression that given a list of values, returns True if:
all values are either strings or integers
if the value is an integer, than it should be less than 5
if the value is a string, it should start with foo
This will show a couple of concepts and useful standard predicates.
Let’s start with the first requirement. We start first with the code and then explain it:
from predicate import all_p, is_int_p, is_str_p
predicate = all_p(is_int_p | is_str_p)
In the example above we import 3 predicates. The all_p
accepts one parameter, which is another predicate. The
is_int_p
checks if a value is an integer. And finally the is_str_p
checks if a value is a string.
We combine the is_int_p
and is_str_p
with the |
operator, resulting in a new predicate.
This is sufficient for the first requirement. Now lets restrict the allowed value of the integer to less than 5:
from predicate import all_p, is_int_p, is_str_p, lt_p
is_int_lt_5 = is_int_p & lt_p(5)
predicate = all_p(is_int_lt_5 | is_str_p)
As you can see, we imported the lt_p
predicate. This checks if a given value, is less than the parameter
(5 in this case). We could have made this a one-liner, but we assigned it to a new predicate variable
is_int_lt_5
. With this addition, we have implemented the second requirement.
Finally we turn to the third requirement, which says that if the value is a string, it should start with “foo”. This sounds like an ideal candidate for a regular expression, and indeed such a predicate is also available:
from predicate import all_p, is_int_p, is_str_p, lt_p, regex_p
is_int_lt_5 = is_int_p & lt_p(5)
is_str_foo = is_str_p & regex_p("^foo.*")
predicate = all_p(is_int_lt_5 | is_str_foo)
Now you are ready to check your new predicate against the requirements, for example:
predicate([1, "foo"]) # True
predicate([1, "foo", None]) # False, None is not valid
predicate([5, "foo"]) # False, 5 is to big
predicate([1, "bar"]) # False, "bar" doesn't begin with "foo"
Let’s now reuse this predicate to generate some sample values:
from predicate import generate_true
from more_itertools import take
result = take(10, generate_true(predicate))
Notice that this may take a few seconds. On my system the output was:
[
[],
(1, -1, "foo", "foo!", "foo!", 'foo"'),
{0, -5, "foo"},
["foo5xT", "foo5xT", "foo", "foo ", "foo!", -1],
(0, -1, -1, -1, "foo"),
{"fooU7", "fooRV8TMtF", "foor959aO5"},
[-9, "foo", "foojBA", "foo ", "foo "],
("foohqhdJr", "foou", "foo", 1, 1, 1, 1, 1, 2),
{1, "foo", "foo!", -1, 'foo"'},
["foo", -1, -1, "foo", "foo", "foo ", 0, 'foo"', 'foo"'],
]
If you don’t want to check manually if these values are correct, you can of course use a predicate:
validate = all_p(predicate)
validate(result)
This should result in True
. If not, please submit a bug report.
Tutorial 2
Task: define a predicate expression that given a list of values, returns True if:
any value is a tuple
the first element of that tuple is a uuid
the second element of that tuple is in the set {“foo”, “bar”}
the third element of that tuple is a truthy value (True, 1, etc.)
Let’s start with the first requirement. This is similar to what we saw in tutorial 1:
from predicate import any_p, is_tuple_p
predicate = any_p(is_tuple_p)
The any_p
predicate is similar to the all_p
predicate: it accepts one parameter (which is a predicate) and
returns true iff at least one value satisfies this predicate.
Now lets turn to the other requirements. If it’s a tuple, we need to check the elements in this tuple against three
different predicates. This is exactly what is_tuple_of_p
does for us. Lets show the code first:
from predicate import any_p, in_p, is_truthy_p, is_tuple_of_p, is_uuid_p
foo_or_bar = in_p("foo", "bar")
valid_tuple = is_tuple_of_p(is_uuid_p, foo_or_bar, is_truthy_p)
predicate = any_p(valid_tuple)
Now you are ready to check your new predicate against the requirements, for example:
from uuid import uuid4
predicate([(uuid4(), "foo", 1)]) # True: 1 is a truthy value
predicate([(uuid4(), "meh", 1)]) # False: missing "foo" or "bar"
predicate([("not_a_uuid", "foo", 1)]) # False: missing uuid