graphlib
— Functionality to operate with graph-like structures¶
Código fuente: Lib/graphlib.py
- class graphlib.TopologicalSorter(graph=None)¶
Provee una funcionalidad para ordenar topológicamente un grafo de nodos hashable.
Un ordenamiento topológico es un ordenamiento lineal de los vértices en un grafo de modo que para cada arista dirigida u -> v desde el vértice u al vértice v, el vértice u viene antes del vértice v en el ordenamiento. Por ejemplo, los vértices del grafo pueden representar tareas a realizar y las aristas pueden representar restricciones de que una tarea debe realizarse antes que otra; en este ejemplo, un ordenamiento topológico es solo una secuencia válida para las tareas. Es posible un ordenamiento topológico completo si y solo si el grafo no tiene ciclos dirigidos, es decir, si es un grafo acíclico dirigido.
Si se proporciona el argumento opcional graph, este debe ser un diccionario que represente un grafo acíclico dirigido donde las claves son nodos y los valores son iterables de todos los predecesores de ese nodo en el grafo (los nodos que tienen las aristas que apuntan al valor clave). Se pueden agregar nodos adicionales al grafo utilizando el
add()
method.En el caso general, los pasos necesarios para realizar el ordenamiento de un grafo son los siguientes:
Cree una instancia
TopologicalSorter
con un grafo inicial opcional.Agregue nodos adicionales al grafo.
Llame a
prepare()
en el grafo.Mientras
is_active()
isTrue
, itere sobre los nodos retornados porget_ready()
y procéselos . Llame adone()
en cada nodo a medida que finaliza el procesamiento.
En caso de que sólo se requiera una ordenación inmediata de los nodos del grafo y no haya paralelismo, se puede utilizar directamente el método de conveniencia
TopologicalSorter.static_order()
.>>> graph = {"D": {"B", "C"}, "C": {"A"}, "B": {"A"}} >>> ts = TopologicalSorter(graph) >>> tuple(ts.static_order()) ('A', 'C', 'B', 'D')
La clase está diseñada para soportar fácilmente el procesamiento en paralelo de los nodos a medida que estén listos. Para la instancia:
topological_sorter = TopologicalSorter() # Add nodes to 'topological_sorter'... topological_sorter.prepare() while topological_sorter.is_active(): for node in topological_sorter.get_ready(): # Worker threads or processes take nodes to work on off the # 'task_queue' queue. task_queue.put(node) # When the work for a node is done, workers put the node in # 'finalized_tasks_queue' so we can get more nodes to work on. # The definition of 'is_active()' guarantees that, at this point, at # least one node has been placed on 'task_queue' that hasn't yet # been passed to 'done()', so this blocking 'get()' must (eventually) # succeed. After calling 'done()', we loop back to call 'get_ready()' # again, so put newly freed nodes on 'task_queue' as soon as # logically possible. node = finalized_tasks_queue.get() topological_sorter.done(node)
- add(node, *predecessors)¶
Añade un nuevo nodo y sus predecesores al grafo. Tanto el node como todos los elementos de predecessors deben ser hashable.
Si se llama varias veces con el mismo argumento del nodo, el conjunto de dependencias será la unión de todas las dependencias pasadas.
Es posible añadir un nodo sin dependencias (no se proporciona predecessors) o proporcionar una dependencia dos veces. Si un nodo que no se ha proporcionado antes se incluye entre los predecessors, se añadirá automáticamente al grafo sin predecesores propios.
Provoca
ValueError
si se llama después deprepare()
.
- prepare()¶
Marca el grafo como terminado y comprueba si existen ciclos en el grafo. Si se detecta algún ciclo, se lanzará
CycleError
, pero se puede seguir utilizandoget_ready()
para obtener tantos nodos como sea posible hasta que los ciclos bloqueen más el progreso. Después de una llamada a esta función, el grafo no puede ser modificado, y por lo tanto no se pueden añadir más nodos utilizandoadd()
.
- is_active()¶
Retorna
True
si se puede avanzar más yFalse
en caso contrario. Se puede avanzar si los ciclos no bloquean la resolución y, o bien todavía existen nodos listos que aún no han sido retornados porTopologicalSorter.get_ready()
o el número de nodos marcados conTopologicalSorter.done()
es menor que el número que han sido retornados porTopologicalSorter.get_ready()
.El método
__bool__()
de esta clase defiere a esta función, por lo que en lugar de:if ts.is_active(): ...
es posible hacer simplemente:
if ts: ...
Lanzar
ValueError
si se llama sin llamar previamente aprepare()
.
- done(*nodes)¶
Marca un conjunto de nodos retornados por
TopologicalSorter.get_ready()
como procesados, desbloqueando cualquier sucesor de cada nodo en nodes para ser retornado en el futuro por una llamada aTopologicalSorter.get_ready()
.Lanza
ValueError
si algún nodo de nodes ya ha sido marcado como procesado por una llamada anterior a este método o si un nodo no fue añadido al grafo usandoTopologicalSorter.add()
, si se llama sin llamar aprepare()
o si el nodo aún no ha sido retornado porget_ready()
.
- get_ready()¶
Retorna una
tupla
con todos los nodos que están listos. Inicialmente retorna todos los nodos sin predecesores, y una vez que éstos se marcan como procesados llamando aTopologicalSorter.done()
, las llamadas posteriores devolverán todos los nuevos nodos que tengan todos sus predecesores ya procesados. Una vez que no se puede avanzar más, se retornan tuplas vacías.Lanzar
ValueError
si se llama sin llamar previamente aprepare()
.
- static_order()¶
Retorna un iterable de nodos en un ordenamiento topológico. El uso de este método no requiere llamar a
TopologicalSorter.prepare()
oTopologicalSorter.done()
. Este método es equivalente a:def static_order(self): self.prepare() while self.is_active(): node_group = self.get_ready() yield from node_group self.done(*node_group)
El orden concreto que se retorna puede depender del orden específico en que se insertaron los elementos en el grafo. Por ejemplo:
>>> ts = TopologicalSorter() >>> ts.add(3, 2, 1) >>> ts.add(1, 0) >>> print([*ts.static_order()]) [2, 0, 1, 3] >>> ts2 = TopologicalSorter() >>> ts2.add(1, 0) >>> ts2.add(3, 2, 1) >>> print([*ts2.static_order()]) [0, 2, 1, 3]
Esto se debe a que «0» y «2» están en el mismo nivel en el gráfico (habrían sido retornados en la misma llamada a
get_ready()
) y el orden entre ellos está determinado por el orden de inserción.Si se detecta algún ciclo, se lanzará
CycleError
.
Added in version 3.9.
Excepciones¶
El módulo graphlib
define las siguientes clases de excepción:
- exception graphlib.CycleError¶
Subclase de
ValueError
planteada porTopologicalSorter.prepare()
si existen ciclos en el grafo de trabajo. Si existen múltiples ciclos, sólo se informará de una elección indefinida entre ellos y se incluirá en la excepción.Se puede acceder al ciclo detectado a través del segundo elemento del atributo
args
de la instancia de la excepción y consiste en una lista de nodos, tal que cada nodo este, en el grafo, un predecesor inmediato del siguiente nodo en la lista. En la lista reportada, el primer y el último nodo serán el mismo, para dejar claro que es cíclico.