Conditional handlers and predicates
Sometimes a handler should only fire for some of the nodes that emit an event
— additions but not subtractions, a particular variable name, and so on. Every
event decorator accepts a when= keyword for exactly this:
import ast
import pyccolo as pyc
class TraceAdditions(pyc.BaseTracer):
@pyc.after_binop(when=lambda node: isinstance(node.op, ast.Add))
def handle_add(self, ret, node, *_, **__):
print(f"added -> {ret}")
with TraceAdditions:
pyc.exec("x = 1 + 2\ny = 3 * 4") # only the addition is reported
The when= callable receives the AST node for the instrumented construct and
returns a truthy/falsy value. Because it is handed the node, it can inspect
operator type, identifier names (node.id), literal values, and anything else
in the tree.
The Predicate class
Under the hood, when= values are wrapped in pyccolo.Predicate. You
can also pass a Predicate directly, which unlocks two extra knobs:
static vs. dynamic.
Predicate(condition, static=True)is evaluated once at instrumentation (compile) time — if it is false, no instrumentation is emitted for that node at all, so there is zero runtime cost. A dynamic predicate (the default for raw-node-id predicates) is re-checked at runtime each time the node executes, which is necessary when the decision depends on runtime state.raw node ids.
Predicate(condition, use_raw_node_id=True)passes the node’s integer id rather than the node object, mirroringpyccolo.register_raw_handler().
The sentinels Predicate.TRUE and Predicate.FALSE are always-on and
always-off predicates, handy as building blocks.
Composing predicates
pyccolo.predicate.CompositePredicate combines several predicates with a
reducer. The classmethods CompositePredicate.any and
CompositePredicate.all build the or / and combinations and
short-circuit against the TRUE / FALSE sentinels:
from pyccolo.predicate import CompositePredicate, Predicate
# true if *either* base predicate is true
either = CompositePredicate.any([pred_a, pred_b])
# CompositePredicate.any([Predicate.TRUE, ...]) collapses to Predicate.TRUE
# CompositePredicate.all([]) is Predicate.TRUE, and any([]) is TRUE as well
A composite is considered static only when all of its constituents are static; if any constituent is dynamic, the composite is re-checked at runtime.
For worked examples, see
test/test_predicate.py
and the when= usages in
test/test_handlers.py.