visitor: Tools for traversing nested data structures

Overview

The visitor module provides an extensible visitor pattern, which can be used for traversing, inspecting, and editing nested Python data structures.

The utility class Visitor automatically handles recursion into most common data types, which allows for user code to skip boilerplate and focus on operation logic. This recursion logic is documented in full under the visit() function.

Commonly targeted data structures in font projects and fonttools include:

Commonly implemented operations include:

  • Summarizing

  • Subsetting

  • Scaling

Specializations

The ttLib.ttVisitor module provides the TTVisitor class, which handles common edge cases when using visitors with TTFont objects. For this reason, it should be preferred in that scenario.

Guide

  1. Create a new class extending Visitor.

  2. Register operations for specific types or attributes with the register annotations.

  3. Instantiate the class and call visit() on the target object.

Example code

One can create a visitor class that checks the case of feature names:

>>> from fontTools.feaLib.ast import FeatureNameStatement
>>> from fontTools.misc.visitor import Visitor
>>>
>>> class SpellCheckVisitor(Visitor):
...     found: list[FeatureNameStatement]
...
...     def __init__(self):
...         self.found = []
>>>
>>> @SpellCheckVisitor.register(FeatureNameStatement)
... def visit(visitor: SpellCheckVisitor, statement: FeatureNameStatement):
...     # Find name statements that are not in title case.
...     if statement.string != statement.string.title():
...         visitor.found.append(statement)

This can then be applied on a feature file object:

>>> io = StringIO("""
...     feature ss01 {
...         featureNames {
...             name "Monolinear Grotesque";
...         };
...     } ss01;
...
...     feature ss02 {
...         featureNames {
...             name "Optical Apertures";
...         };
...     } ss02;
...
...     feature ss03 {
...         featureNames {
...             name "slanted Humanism";
...         };
...     } ss03;
... """)
>>> fea = Parser(io).parse()
>>>
>>> visitor = SpellCheckVisitor()
>>> visitor.visit(fea)
>>>
>>> for statement in visitor.found:
...     print(
...         "Found feature name that was not in title case: "
...         f"'{statement.string}' at location '{statement.location}'",
... )
Found feature name that was not in title case: 'slanted Humanism' at location '<features>:16:13'

Package contents

Generic visitor pattern implementation for Python objects.

class fontTools.misc.visitor.Visitor[source]

Bases: object

defaultStop = False
classmethod register(clazzes)[source]
classmethod register_attr(clazzes, attrs)[source]
classmethod register_attrs(clazzes_attrs)[source]
visitObject(obj, *args, **kwargs)[source]

Called to visit an object. This function loops over all non-private attributes of the objects and calls any user-registered (via @register_attr() or @register_attrs()) visit() functions.

The visitor will proceed to call self.visitAttr(), unless there is a user-registered visit function and:

  • It returns False; or

  • It returns None (or doesn’t return anything) and visitor.defaultStop is True (non-default).

visitAttr(obj, attr, value, *args, **kwargs)[source]

Called to visit an attribute of an object.

visitList(obj, *args, **kwargs)[source]

Called to visit any value that is a list.

visitDict(obj, *args, **kwargs)[source]

Called to visit any value that is a dictionary.

visitLeaf(obj, *args, **kwargs)[source]

Called to visit any value that is not an object, list, or dictionary.

visit(obj, *args, **kwargs)[source]

This is the main entry to the visitor. The visitor will visit object obj.

The visitor will first determine if there is a registered (via @register()) visit function for the type of object. If there is, it will be called, and (visitor, obj, *args, **kwargs) will be passed to the user visit function.

The visitor will not recurse if there is a user-registered visit function and:

  • It returns False; or

  • It returns None (or doesn’t return anything) and visitor.defaultStop is True (non-default)

Otherwise, the visitor will proceed to dispatch to one of self.visitObject(), self.visitList(), self.visitDict(), or self.visitLeaf() (any of which can be overriden in a subclass).