Instrumentation
This is a powerful feature that allows you to do a run-time check on any given function.
Easiest way to explain this is by example.
from predicate import instrument_function, Spec, is_int_p
def max_int_with_bug(x, y):
return x if x > y else f"{y}"
spec: Spec = {
"args": {"x": is_int_p, "y": is_int_p},
"ret": is_int_p,
"fn": lambda x, y, ret: ret >= x and ret >= y,
}
instrument_function(max_int_with_bug, spec=spec)
The function max_int_with_bugs contains an annoying bug. As long as x is the largest value, everything is fine, but if y is the maximum, then suddenly a string is returned instead of an integer.
Looking at the specification, you first see the args
keyword. This defines the predicates for the
arguments. If the arguments are annotated, a specification will be derived from that annotation.
For example ‘x: int’ will automatically result in is_int_p.
The next keyword is ret
. This specifies the predicate that the return value will be evaluated against.
In this example we define that it must be an int.
And finally the optional fn
keyword defines how the input and the return value relate.
Given this example we can try:
result = max_int_with_bug(3, 4)
This is going to be fine since the parameters and the resulting value all satisfy the constraints.
However,
result = max_int_with_bug(4, 3)
Will trigger the faulty behaviour. You will probably see something like:
ValueError: Return predicate for function max_int_with_bug failed.