Quickstart

This page walks through the “Hello, world!” tracer from the front page in detail, and explains the one piece of Pyccolo that surprises newcomers: why the traced code is wrapped in pyccolo.exec().

Hello, world!

import pyccolo as pyc


class HelloTracer(pyc.BaseTracer):
    @pyc.before_stmt
    def handle_stmt(self, *_, **__):
        print("Hello, world!")


if __name__ == "__main__":
    with HelloTracer:
        # prints "Hello, world!" 11 times
        pyc.exec("for _ in range(10): pass")

Instrumentation is provided by a tracer class that inherits from pyccolo.BaseTracer. Subclassing it rewrites Python source code with instrumentation that fires whenever events of interest occur — here, whenever a statement is about to execute. By registering a handler for the associated event (with the @pyc.before_stmt decorator), we enrich the program with additional observability, or even alter its behavior altogether.

A tracer class is used as a context manager. Inside the with HelloTracer: block, instrumentation is active; outside it, ordinary Python runs unchanged. (The class itself is the context manager — you do not instantiate it.)

Why pyc.exec(...)?

A program’s abstract syntax tree is fixed at import / compile time. When our script initially started running, the tracer was not active, so unquoted Python in the same file will lack instrumentation — the eleven “Hello, world!” lines come from the code passed to pyccolo.exec(), not from the for loop being written directly in the module body.

It is possible to instrument modules at import time (see Instrumenting imported modules), but only when the imports are performed inside a tracing context. Thus, to instrument code appearing in the same module where the tracer class is defined, we must “quote” it — pass it as a string (or a pre-parsed AST) to pyccolo.exec(), pyccolo.eval(), or pyccolo.execute().

pyccolo.exec() returns the resulting namespace as a dictionary, so you can inspect what the traced code produced:

with HelloTracer:
    env = pyc.exec("x = 1\ny = x + 1")

assert env["y"] == 2

Note that sys-level events (call, line, return_, exception, opcode) do not require pyc.exec, because they do not involve any AST-level transformation — see Compatibility with sys.settrace.

Where to go next