graphlib
— Функциональность для работы с графоподобными структурами¶
Исходный код: Lib/graphlib.py
- class graphlib.TopologicalSorter(graph=None)¶
Предоставляет функциональные возможности для топологической сортировки графа из hashable узлов.
Топологический порядок - это линейное упорядочение вершин в графе таким образом, что для каждого направленного ребра u -> v из вершины u в вершину v вершина u стоит перед вершиной v в порядке упорядочения. Например, вершины графа могут представлять задачи, которые необходимо выполнить, а ребра могут представлять ограничения, согласно которым одна задача должна быть выполнена перед другой; в этом примере топологический порядок - это просто допустимая последовательность для задач. Полное топологическое упорядочение возможно тогда и только тогда, когда граф не имеет направленных циклов, то есть если это направленный ациклический граф.
Если указан необязательный аргумент graph, то это должен быть словарь, представляющий ориентированный ациклический граф, где ключами являются узлы, а значениями - повторяемые значения всех предшественников этого узла в графе (узлы, ребра которых указывают на значение в ключе). Дополнительные узлы могут быть добавлены к графу с помощью метода
add()
.В общем случае для выполнения сортировки данного графа требуются следующие шаги:
Создайте экземпляр
TopologicalSorter
с необязательным начальным графиком.Добавьте дополнительные узлы к графику.
Вызовите
prepare()
на графике.Пока
is_active()
равноTrue
, выполните итерацию по узлам, возвращаемымget_ready()
, и обработайте их. Вызовитеdone()
на каждом узле по мере завершения обработки.
В случае, если требуется только немедленная сортировка узлов на графике и параллелизм не задействован, можно напрямую использовать удобный метод
TopologicalSorter.static_order()
:>>> graph = {"D": {"B", "C"}, "C": {"A"}, "B": {"A"}} >>> ts = TopologicalSorter(graph) >>> tuple(ts.static_order()) ('A', 'C', 'B', 'D')
Класс разработан таким образом, чтобы легко поддерживать параллельную обработку узлов по мере их готовности. Например:
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)¶
Добавьте в график новый узел и его предшественники. Как узел, так и все элементы в предшественниках должны быть hashable.
При многократном вызове с одним и тем же аргументом node набор зависимостей будет представлять собой объединение всех переданных зависимостей.
Можно добавить узел без зависимостей (предшественники не указаны) или указать зависимость дважды. Если узел, который не был указан ранее, включен в число предшественников, он будет автоматически добавлен в график без собственных предшественников.
Вызывает
ValueError
, если вызывается послеprepare()
.
- prepare()¶
Отметьте график как законченный и проверьте наличие циклов на графике. Если обнаружен какой-либо цикл, будет выведено значение
CycleError
, ноget_ready()
все еще можно использовать для получения как можно большего количества узлов, пока циклы не заблокируют дальнейший прогресс. После вызова этой функции график не может быть изменен, и поэтому больше никаких узлов нельзя добавить с помощьюadd()
.
- is_active()¶
Возвращает
True
, если можно добиться большего прогресса, иFalse
в противном случае. Прогресс может быть достигнут, если циклы не блокируют разрешение и либо все еще есть готовые узлы, которые еще не были возвращены с помощьюTopologicalSorter.get_ready()
, либо количество узлов, помеченных какTopologicalSorter.done()
, меньше, чем количество узлов, возвращенных с помощьюTopologicalSorter.get_ready()
.Метод
__bool__()
этого класса подчиняется этой функции, поэтому вместо:if ts.is_active(): ...
это можно просто сделать:
if ts: ...
Вызывает
ValueError
, если вызывается без предварительного вызоваprepare()
.
- done(*nodes)¶
Помечает набор узлов, возвращенных
TopologicalSorter.get_ready()
как обработанные, разблокируя любого преемника каждого узла в nodes для возврата в будущем вызовомTopologicalSorter.get_ready()
.Вызывает
ValueError
, если какой-либо узел в nodes уже был помечен как обработанный предыдущим вызовом этого метода, или если узел не был добавлен в график с помощьюTopologicalSorter.add()
, если он был вызван без вызоваprepare()
или если узел еще не был возвращен с помощьюget_ready()
.
- get_ready()¶
Возвращает
tuple
со всеми готовыми узлами. Первоначально он возвращает все узлы, не имеющие предшественников, и как только они помечены как обработанные вызовомTopologicalSorter.done()
, дальнейшие вызовы будут возвращать все новые узлы, все предшественники которых уже обработаны. Как только дальнейший прогресс невозможен, возвращаются пустые кортежи.Вызывает
ValueError
, если вызывается без предварительного вызоваprepare()
.
- static_order()¶
Возвращает объект-итератор, который выполняет итерацию по узлам в топологическом порядке. При использовании этого метода не следует вызывать
prepare()
иdone()
. Этот метод эквивалентен:def static_order(self): self.prepare() while self.is_active(): node_group = self.get_ready() yield from node_group self.done(*node_group)
Конкретный возвращаемый порядок может зависеть от конкретного порядка, в котором элементы были вставлены в график. Например:
>>> 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]
Это связано с тем, что «0» и «2» находятся на одном уровне в графе (они были бы возвращены при одном и том же вызове
get_ready()
), и порядок между ними определяется порядком вставки.Если будет обнаружен какой-либо цикл, будет поднят
CycleError
.
Добавлено в версии 3.9.
Исключения¶
Модуль graphlib
определяет следующие классы исключений:
- exception graphlib.CycleError¶
Подкласс
ValueError
создается с помощьюTopologicalSorter.prepare()
, если в рабочем графе существуют циклы. Если существует несколько циклов, будет сообщен только один неопределенный вариант из них, который будет включен в исключение.Обнаруженный цикл может быть доступен через второй элемент в атрибуте
args
экземпляра исключения и состоит из списка узлов, так что каждый узел в графе является непосредственным предшественником следующего узла в списке. В представленном списке первый и последний узлы будут одинаковыми, чтобы было ясно, что он является циклическим.