Predicates
A predicate is the object behind a handler’s when= option: it decides, per
node, whether the handler should fire. You can pass a plain callable of the node,
but wrapping it in a Predicate gives you control over when the decision
is made (compile time vs. runtime) and lets you compose conditions. For the
task-oriented walkthrough, see Observe and override values.
- class pyccolo.Predicate(condition: Callable[[AST], bool], use_raw_node_id: Literal[False], static: bool = True)[source]
- class pyccolo.Predicate(condition: Callable[[int], bool], use_raw_node_id: Literal[True], static: bool = False)
- class pyccolo.Predicate(condition: Callable[[...], bool], use_raw_node_id: bool = False, static: bool = False)
static=Truepredicates are evaluated once, at instrument time, and the result is baked into the rewrite — zero runtime cost, but the node must be decidable statically.static=Falsepredicates re-run on every event (viadynamic_call()).use_raw_node_id=Truepasses the integer node id to your condition instead of the resolvedast.ASTnode.Predicate.TRUE/Predicate.FALSEare ready-made constants.
A dynamic predicate re-checks each occurrence; here the handler fires only for
addition nodes, and only 1 + 2 matches:
fired = []
class OnlyAdd(pyc.BaseTracer):
@pyc.after_binop(when=pyc.Predicate(lambda node: isinstance(node.op, ast.Add)))
def handle(self, ret, node, *_, **__):
fired.append(type(node.op).__name__)
return ret
with OnlyAdd:
pyc.exec("a = 1 + 2\nb = 3 * 4")
assert fired == ["Add"]
Marking the same predicate static=True resolves it during the rewrite instead,
so a non-matching handler costs nothing at runtime. Use static whenever the
decision depends only on the shape of the syntax tree.
Composing predicates
- class pyccolo.predicate.CompositePredicate(base_predicates: ~typing.Sequence[~pyccolo.predicate.Predicate], reducer=<built-in function any>)[source]
any() /
all() combine predicates into one you
can pass as when=. They short-circuit against Predicate.TRUE /
Predicate.FALSE, and the result stays static only if every input is
static:
from pyccolo.predicate import CompositePredicate
is_add = pyc.Predicate(lambda node: isinstance(node.op, ast.Add), static=True)
is_sub = pyc.Predicate(lambda node: isinstance(node.op, ast.Sub), static=True)
add_or_sub = CompositePredicate.any([is_add, is_sub])
seen = []
class AddOrSub(pyc.BaseTracer):
@pyc.register_handler(pyc.after_binop, when=add_or_sub)
def handle(self, ret, node, *_, **__):
seen.append(type(node.op).__name__)
return ret
with AddOrSub:
pyc.exec("a = 1 + 2\nb = 5 - 1\nc = 2 * 3")
assert seen == ["Add", "Sub"]
For a one-off combination you can also just write the disjunction inline —
when=lambda node: isinstance(node.op, (ast.Add, ast.Sub)) — and reach for
CompositePredicate when you want to build predicates up programmatically or
preserve staticness across the combination.