ExampleDifficulty: Intermediate1 min

Visitor in the Wild: Python's ast.NodeVisitor

Subclass it, write visit_Call, and it is called for every function call in a parsed program.

Python's standard library uses the Visitor pattern to walk parsed programs. ast.parse turns source code into an abstract syntax tree, a tree of objects in which each node is one part of the program, such as a call or a loop. ast.NodeVisitor visits every node, and calls the subclass's method for that node's class. This card counts the function calls in a short program with it.

py
import ast
from collections import Counter

SOURCE = """
names = load("users.csv")
for name in sorted(names):
    print(name.title())
print(len(names))
"""


class CallCounter(ast.NodeVisitor):
    def __init__(self) -> None:
        self.calls: Counter[str] = Counter()

    def visit_Call(self, node: ast.Call) -> None:
        self.calls[ast.unparse(node.func)] += 1
        self.generic_visit(node)


counter = CallCounter()
counter.visit(ast.parse(SOURCE))
for name, count in counter.calls.most_common():
    print(f"{name}: {count}")
OutputRan with python:3.12-slim (Python 3.12.13)
print: 2
load: 1
sorted: 1
name.title: 1
len: 1
`visit_Call` runs for every `ast.Call` node in the tree.

The program calls print twice and four other functions once each, including the method call name.title. The visitor found them all, including the call nested inside another call, because generic_visit carries on into the node's children; without that line, the name.title() inside print would be missed.

What NodeVisitor shares with the usual form of the pattern is one class per operation, with a method per element class, called once per element. The difference is how the method is found. NodeVisitor has no accept on the node classes: its visit method builds the name visit_ plus the node's class name, and calls that method if the subclass defines it, falling back to generic_visit if not. A visitor that defines only visit_Call therefore ignores every other kind of node without writing a method for each.

Tools that inspect Python source, such as linters, walk the same trees, often with one visitor class per check.