bpo-17005: Minor improvements to the documentation of TopologicalSorter (GH-18155)

This commit is contained in:
Pablo Galindo 2020-01-23 21:01:50 +00:00 committed by GitHub
parent 7142df5ea2
commit 65ecc390c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -522,54 +522,46 @@ The :mod:`functools` module defines the following functions:
Provides functionality to topologically sort a graph of hashable nodes. Provides functionality to topologically sort a graph of hashable nodes.
A topological order is a linear ordering of the vertices in a graph such that for A topological order is a linear ordering of the vertices in a graph such that
every directed edge u -> v from vertex u to vertex v, vertex u comes before vertex for every directed edge u -> v from vertex u to vertex v, vertex u comes
v in the ordering. For instance, the vertices of the graph may represent tasks to before vertex v in the ordering. For instance, the vertices of the graph may
be performed, and the edges may represent constraints that one task must be represent tasks to be performed, and the edges may represent constraints that
performed before another; in this example, a topological ordering is just a valid one task must be performed before another; in this example, a topological
sequence for the tasks. A complete topological ordering is possible if and only if ordering is just a valid sequence for the tasks. A complete topological
the graph has no directed cycles, that is, if it is a directed acyclic graph. ordering is possible if and only if the graph has no directed cycles, that
is, if it is a directed acyclic graph.
If the optional *graph* argument is provided it must be a dictionary representing If the optional *graph* argument is provided it must be a dictionary
a directed acyclic graph where the keys are nodes and the values are iterables of representing a directed acyclic graph where the keys are nodes and the values
all predecessors of that node in the graph (the nodes that have edges that point are iterables of all predecessors of that node in the graph (the nodes that
to the value in the key). Additional nodes can be added to the graph using the have edges that point to the value in the key). Additional nodes can be added
:meth:`~TopologicalSorter.add` method. to the graph using the :meth:`~TopologicalSorter.add` method.
In the general case, the steps required to perform the sorting of a given graph In the general case, the steps required to perform the sorting of a given
are as follows: graph are as follows:
* Create an instance of the :class:`TopologicalSorter` with an optional initial graph. * Create an instance of the :class:`TopologicalSorter` with an optional
initial graph.
* Add additional nodes to the graph. * Add additional nodes to the graph.
* Call :meth:`~TopologicalSorter.prepare` on the graph. * Call :meth:`~TopologicalSorter.prepare` on the graph.
* While :meth:`~TopologicalSorter.is_active` is ``True``, iterate over the * While :meth:`~TopologicalSorter.is_active` is ``True``, iterate over
nodes returned by :meth:`~TopologicalSorter.get_ready` and process them. the nodes returned by :meth:`~TopologicalSorter.get_ready` and
Call :meth:`~TopologicalSorter.done` on each node as it finishes processing. process them. Call :meth:`~TopologicalSorter.done` on each node as it
finishes processing.
In case just an immediate sorting of the nodes in the graph is required and In case just an immediate sorting of the nodes in the graph is required and
no parallelism is involved, the convenience method :meth:`TopologicalSorter.static_order` no parallelism is involved, the convenience method
can be used directly. For example, this method can be used to implement a simple :meth:`TopologicalSorter.static_order` can be used directly:
version of the C3 linearization algorithm used by Python to calculate the Method
Resolution Order (MRO) of a derived class:
.. doctest:: .. doctest::
>>> class A: pass >>> graph = {"D": {"B", "C"}, "C": {"A"}, "B": {"A"}}
>>> class B(A): pass
>>> class C(A): pass
>>> class D(B, C): pass
>>> D.__mro__
(<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
>>> graph = {D: {B, C}, C: {A}, B: {A}, A:{object}}
>>> ts = TopologicalSorter(graph) >>> ts = TopologicalSorter(graph)
>>> topological_order = tuple(ts.static_order()) >>> tuple(ts.static_order())
>>> tuple(reversed(topological_order)) ('A', 'C', 'B', 'D')
(<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
The class is designed to easily support parallel processing of the nodes as they The class is designed to easily support parallel processing of the nodes as
become ready. For instance:: they become ready. For instance::
topological_sorter = TopologicalSorter() topological_sorter = TopologicalSorter()
@ -595,39 +587,39 @@ The :mod:`functools` module defines the following functions:
.. method:: add(node, *predecessors) .. method:: add(node, *predecessors)
Add a new node and its predecessors to the graph. Both the *node* and Add a new node and its predecessors to the graph. Both the *node* and all
all elements in *predecessors* must be hashable. elements in *predecessors* must be hashable.
If called multiple times with the same node argument, the set of dependencies If called multiple times with the same node argument, the set of
will be the union of all dependencies passed in. dependencies will be the union of all dependencies passed in.
It is possible to add a node with no dependencies (*predecessors* is not It is possible to add a node with no dependencies (*predecessors* is not
provided) or to provide a dependency twice. If a node that has not been provided) or to provide a dependency twice. If a node that has not been
provided before is included among *predecessors* it will be automatically added provided before is included among *predecessors* it will be automatically
to the graph with no predecessors of its own. added to the graph with no predecessors of its own.
Raises :exc:`ValueError` if called after :meth:`~TopologicalSorter.prepare`. Raises :exc:`ValueError` if called after :meth:`~TopologicalSorter.prepare`.
.. method:: prepare() .. method:: prepare()
Mark the graph as finished and check for cycles in the graph. If any cycle is Mark the graph as finished and check for cycles in the graph. If any cycle
detected, :exc:`CycleError` will be raised, but is detected, :exc:`CycleError` will be raised, but
:meth:`~TopologicalSorter.get_ready` can still be used to obtain as many nodes :meth:`~TopologicalSorter.get_ready` can still be used to obtain as many
as possible until cycles block more progress. After a call to this function, nodes as possible until cycles block more progress. After a call to this
the graph cannot be modified, and therefore no more nodes can be added using function, the graph cannot be modified, and therefore no more nodes can be
:meth:`~TopologicalSorter.add`. added using :meth:`~TopologicalSorter.add`.
.. method:: is_active() .. method:: is_active()
Returns ``True`` if more progress can be made and ``False`` otherwise. Progress Returns ``True`` if more progress can be made and ``False`` otherwise.
can be made if cycles do not block the resolution and either there are still Progress can be made if cycles do not block the resolution and either
nodes ready that haven't yet been returned by there are still nodes ready that haven't yet been returned by
:meth:`TopologicalSorter.get_ready` or the number of nodes marked :meth:`TopologicalSorter.get_ready` or the number of nodes marked
:meth:`TopologicalSorter.done` is less than the number that have been returned :meth:`TopologicalSorter.done` is less than the number that have been
by :meth:`TopologicalSorter.get_ready`. returned by :meth:`TopologicalSorter.get_ready`.
The :meth:`~TopologicalSorter.__bool__` method of this class defers to this The :meth:`~TopologicalSorter.__bool__` method of this class defers to
function, so instead of:: this function, so instead of::
if ts.is_active(): if ts.is_active():
... ...
@ -637,29 +629,28 @@ The :mod:`functools` module defines the following functions:
if ts: if ts:
... ...
Raises :exc:`ValueError` if called without calling :meth:`~TopologicalSorter.prepare` Raises :exc:`ValueError` if called without calling
previously. :meth:`~TopologicalSorter.prepare` previously.
.. method:: done(*nodes) .. method:: done(*nodes)
Marks a set of nodes returned by :meth:`TopologicalSorter.get_ready` as Marks a set of nodes returned by :meth:`TopologicalSorter.get_ready` as
processed, unblocking any successor of each node in *nodes* for being returned processed, unblocking any successor of each node in *nodes* for being
in the future by a call to :meth:`TopologicalSorter.get_ready`. returned in the future by a call to :meth:`TopologicalSorter.get_ready`.
Raises :exc:`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 or if a node was not added to the processed by a previous call to this method or if a node was not added to
graph by using :meth:`TopologicalSorter.add`, if called without calling the graph by using :meth:`TopologicalSorter.add`, if called without
:meth:`~TopologicalSorter.prepare` or if node has not yet been returned by calling :meth:`~TopologicalSorter.prepare` or if node has not yet been
:meth:`~TopologicalSorter.get_ready`. returned by :meth:`~TopologicalSorter.get_ready`.
.. method:: get_ready() .. method:: get_ready()
Returns a ``tuple`` with all the nodes that are ready. Initially it returns all Returns a ``tuple`` with all the nodes that are ready. Initially it
nodes with no predecessors, and once those are marked as processed by calling returns all nodes with no predecessors, and once those are marked as
:meth:`TopologicalSorter.done`, further calls will return all new nodes that processed by calling :meth:`TopologicalSorter.done`, further calls will
have all their predecessors already processed. Once no more progress can be return all new nodes that have all their predecessors already processed.
made, empty tuples are returned. Once no more progress can be made, empty tuples are returned.
made.
Raises :exc:`ValueError` if called without calling Raises :exc:`ValueError` if called without calling
:meth:`~TopologicalSorter.prepare` previously. :meth:`~TopologicalSorter.prepare` previously.
@ -694,9 +685,10 @@ The :mod:`functools` module defines the following functions:
>>> print([*ts2.static_order()]) >>> print([*ts2.static_order()])
[0, 2, 1, 3] [0, 2, 1, 3]
This is due to the fact that "0" and "2" are in the same level in the graph (they This is due to the fact that "0" and "2" are in the same level in the
would have been returned in the same call to :meth:`~TopologicalSorter.get_ready`) graph (they would have been returned in the same call to
and the order between them is determined by the order of insertion. :meth:`~TopologicalSorter.get_ready`) and the order between them is
determined by the order of insertion.
If any cycle is detected, :exc:`CycleError` will be raised. If any cycle is detected, :exc:`CycleError` will be raised.