diff --git a/Lib/graphlib.py b/Lib/graphlib.py index 636545648e12d3..19a95d5edfd4d8 100644 --- a/Lib/graphlib.py +++ b/Lib/graphlib.py @@ -24,15 +24,13 @@ def __init__(self, node): class CycleError(ValueError): - """Subclass of ValueError raised by TopologicalSorter.prepare if cycles - exist in the working graph. - - If multiple cycles exist, only one undefined choice among them will be reported - and included in the exception. The detected cycle can be accessed via the second - element in the *args* attribute of the exception instance and consists in a list - of nodes, such that each node is, in the graph, an immediate predecessor of the - next node in the list. In the reported list, the first and the last node will be - the same, to make it clear that it is cyclic. + """Subclass of ValueError raised by TopologicalSorter when cycles exist in the graph. + + When a cycle is detected, only one arbitrary cycle will be reported and included in the + exception. The cycle can be accessed via the second element in the *args* + attribute of the exception instance. The cycle is represented as a list of nodes + where each node is a direct predecessor of the next node in the list. The first + and last nodes in the list are identical to indicate the cycle's completion. """ pass @@ -85,11 +83,16 @@ def add(self, node, *predecessors): def prepare(self): """Mark the graph as finished and check for cycles in the graph. - - If any cycle is detected, "CycleError" will be raised, but "get_ready" can - still be used to obtain as many nodes as possible until cycles block more - progress. After a call to this function, the graph cannot be modified and - therefore no more nodes can be added using "add". + This method must be called exactly once, after all nodes are added and + before calling get_ready(). It checks for cycles and initializes the + internal data structures for processing. + + Even if a cycle is detected and CycleError is raised, the graph can still + be processed up to the nodes involved in the cycle. + + Raises: + CycleError: If any cycles are detected in the graph. + ValueError: If prepare() has already been called. """ if self._ready_nodes is not None: raise ValueError("cannot prepare() more than once") @@ -101,14 +104,13 @@ def prepare(self): # if the user wants to catch the CycleError, that's fine, # they can continue using the instance to grab as many # nodes as possible before cycles block more progress - cycle = self._find_cycle() - if cycle: - raise CycleError(f"nodes are in a cycle", cycle) + if (cycle := self._find_cycle()): + raise CycleError("nodes are in a cycle", cycle) def get_ready(self): - """Return a tuple of all the nodes that are ready. + """Return a tuple of all the ready nodes. - Initially it returns all nodes with no predecessors; once those are marked + Initially, it returns all nodes with no predecessors; once those are marked as processed by calling "done", further calls will return all new nodes that have all their predecessors already processed. Once no more progress can be made, empty tuples are returned. @@ -134,7 +136,7 @@ def get_ready(self): def is_active(self): """Return ``True`` if more progress can be made and ``False`` otherwise. - Progress can be made if cycles do not block the resolution and either there + Progress can be made if cycles do not block the resolution and if there are still nodes ready that haven't yet been returned by "get_ready" or the number of nodes marked "done" is less than the number that have been returned by "get_ready". @@ -154,7 +156,7 @@ def done(self, *nodes): This method unblocks any successor of each node in *nodes* for being returned in the future by a call to "get_ready". - Raises :exec:`ValueError` if any node in *nodes* has already been marked as + Raises :exc:`ValueError` if any node in *nodes* has already been marked as processed by a previous call to this method, if a node was not added to the graph by using "add" or if called without calling "prepare" previously or if node has not yet been returned by "get_ready". @@ -186,8 +188,8 @@ def done(self, *nodes): # Mark the node as processed nodeinfo.npredecessors = _NODE_DONE - # Go to all the successors and reduce the number of predecessors, collecting all the ones - # that are ready to be returned in the next get_ready() call. + # Go to all the successors and reduce the number of predecessors, + # collecting all the ones that are ready to be returned in the next get_ready() call. for successor in nodeinfo.successors: successor_info = n2i[successor] successor_info.npredecessors -= 1 @@ -233,7 +235,7 @@ def _find_cycle(self): return None def static_order(self): - """Returns an iterable of nodes in a topological order. + """Returns an iterable of nodes in topological order. The particular order that is returned may depend on the specific order in which the items were inserted in the graph.