Lib/graphlib.py
cpython 3.14 @ ab2d84fe1023/Lib/graphlib.py
graphlib provides TopologicalSorter, a single class that implements Kahn's algorithm for topologically ordering the nodes of a directed acyclic graph (DAG). The graph is described in terms of predecessors: calling add(node, *predecessors) declares that node must come after each listed predecessor. Nodes and predecessors must be hashable; any value that can be a dict key works.
The class supports two usage modes. In static mode, static_order() handles the full prepare / get_ready / done cycle internally and yields nodes one at a time in a valid topological order. In parallel mode the caller drives the cycle manually, calling get_ready() to obtain a batch of nodes that are all safe to process concurrently, then done(*processed) to unblock their successors. The is_active() predicate (also exposed as __bool__) tells the caller whether any nodes remain.
If the graph contains a cycle, prepare() raises CycleError (a ValueError subclass). The exception's second args element is the detected cycle as a list of nodes where the first and last entries are identical. Partial progress is still possible after catching CycleError: get_ready() will continue returning non-cyclic nodes until no more can be unblocked.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 9-23 | _NodeInfo | Slotted helper: node, npredecessors, successors | |
| 26-38 | CycleError | ValueError subclass; cycle path in args[1] | |
| 44-53 | TopologicalSorter.__init__ | Initialize node map, ready list, counters; optionally load a dict graph | |
| 59-84 | TopologicalSorter.add | Register a node and its predecessor edges | |
| 86-110 | TopologicalSorter.prepare | Freeze the graph, populate initial ready list, detect cycles | |
| 112-136 | TopologicalSorter.get_ready | Return a tuple of all currently unblocked nodes | |
| 138-153 | TopologicalSorter.is_active | Return True if unsatisfied nodes remain | |
| 155-200 | TopologicalSorter.done | Mark nodes processed; decrement successor predecessor counts | |
| 202-237 | TopologicalSorter._find_cycle | DFS cycle detection; returns cycle path or None | |
| 239-252 | TopologicalSorter.static_order | Generator wrapping the parallel API for simple sequential use |
Reading
_NodeInfo and internal state constants (lines 1 to 23)
cpython 3.14 @ ab2d84fe1023/Lib/graphlib.py#L1-23
Each node in the graph is tracked by a _NodeInfo instance stored in self._node2info. The __slots__ declaration keeps memory tight. npredecessors starts as the count of unsatisfied predecessors; it is decremented by done() as predecessors complete. Two sentinel values signal special states: _NODE_OUT = -1 means the node was returned by get_ready but not yet marked done, and _NODE_DONE = -2 means it has been fully processed.
add and prepare (lines 59 to 110)
cpython 3.14 @ ab2d84fe1023/Lib/graphlib.py#L59-110
add(node, *predecessors) updates two sets of edges at once. For the new node it increments npredecessors by the number of predecessors. For each predecessor it appends node to pred_info.successors. This dual bookkeeping is what makes done() O(successors) rather than O(nodes).
prepare() collects all nodes where npredecessors == 0 into self._ready_nodes (a plain list used as a queue), then calls _find_cycle. The ready-list is populated before cycle detection so that a caller who catches CycleError can still drain non-cyclic nodes.
ts = TopologicalSorter()
ts.add("C", "A", "B")
ts.add("D", "B")
ts.prepare()