我正在编写一个Python模块来收集和安排夜间自动更新的任务,并可选择以交互方式执行各个任务。 (夜间更新无条件地执行所有任务。)
其中一些单独的任务依赖于另一个任务的执行来正常运行:例如如果任务A被安排(自动或手动)但任务B没有安排,那么必须在A之前将任务B添加到计划中。我将这些称为强依赖,因为他们& #34;拉入"必要时开展新任务。
其他任务取决于另一个任务的输出,但如果未安排该任务,也可以对旧输出进行操作;例如如果任务C已安排但任务D未安排,则D 未添加,C运行旧数据。我将这些称为弱依赖,因为它们太弱了"更改时间表。
我使用nx.DiGraph
成功实现了强依赖关系来构建依赖关系,并nx.topological_sort
生成最终任务调度(将请求的任务作为起始节点)。但是,我不确定如何实现弱依赖,因为topological_sort
似乎总是添加由任何边连接的节点。我甚至试过设置边缘' weight
为0,但没有运气。
编辑:以下是我目前实施的一个粗略示例。
# tasklib.py
task_graph = nx.DiGraph()
def run(start_tasks):
global task_graph
for modname in glob.glob('modules/*.py'):
__import__(os.path.splitext(modname)[0].replace('/','.'))
task_queue = nx.topological_sort(task_graph, start_tasks)
# Remove weakly-linked tasks here
for t in task_queue:
task_graph.node[t]['func']()
def task(func):
task_graph.add_node(func.__name__, func=func)
return func
def depends(*args): # strong dependency
def add_deps(func):
for dep in args:
task_graph.add_edge(func.__name__, dep)
return func
return add_deps
def after(*args): # weak dependency
def add_deps(func):
for dep in args:
task_graph.add_edge(func.__name__, dep, weak=True)
return func
return add_deps
# modules/mytasks.py
from tasklib import task, depends, after
@task
def process_data():
# do some lengthy processing
@task
@depends('process_data')
def process_more_data():
# do even more processing on the above output
@task
@after('process_data')
def generate_report():
# generate a report on the processed data
此处,process_more_data
需要process_data
的最新输出才能继续工作,因此它使用强依赖性。另一方面,generate_report
可以使用旧数据自行运行,但如果还安排了process_data
,则在新结果生成旧数据报告时没有意义即将生产;它不应该运行到之后。
根据@ BrenBarn的建议,我可能会尝试向run
添加一个额外步骤,以便从task_queue
删除弱链接的任务,除非它们也存在于start_tasks
中。
我的问题归结为:如果两个节点都在起始节点集中,我是否可以创建参与排序的边缘?如果是这样,怎么样?如果没有,是否有将实现此效果的替代方法或库?
我考虑过为自己的目的编写一个带有此功能的精简网络库,但我希望有更好的方法。
除此之外,我还想支持"后依赖关系,"其中一个任务意味着依赖任务在之后运行 - 但我担心这超出了topo排序的范围。
答案 0 :(得分:0)
一种方法是构建包含A
和B
之间的有向边的依赖关系图,当且仅当A
强烈依赖于B
或A
时弱依赖于B
而B
强烈依赖于其他任务。
这里有一些代码可以做到这一点。在此示例中,我们将考虑六个任务,a
到f
:
import networkx as nx
deps = nx.DiGraph()
deps.add_nodes_from("abcdef")
deps.add_edges_from([("a","b"), ("c","a"), ("e","c")], is_strong=True)
deps.add_edges_from([("f","c"), ("d","c")], is_strong=False)
我们可以理解为a
强烈要求b
等,而f
是c
的弱要求。用户将选择要运行的任务a
和c
。由于任务a
已运行,我们需要c
才能在其之前运行,因此还需要e
才能在c
之前运行。任务d
没有强依赖关系,但它是c
的弱依赖关系。因此,我们希望按顺序运行任务:d, e, c, a
。
首先,我们将通过抛弃所有弱边缘来提取诱导的子图。我们将在此图中查看每个计划任务的祖先:这是我们需要运行的依赖集。然后我们将采用由这些节点引起的完整依赖图的子图。此图表上的topo排序产生了我们想要的解决方案。
以下是代码:
def construct_task_graph(deps, tasks):
required = set(tasks)
strong_subgraph = extract_strong_subgraph(deps)
for task in tasks:
ancestors = nx.ancestors(strong_subgraph, task)
required.update(ancestors)
return nx.subgraph(deps, required)
def extract_strong_subgraph(deps):
strong_deps = nx.DiGraph()
strong_deps.add_nodes_from(deps)
strong_edges = (e for e in deps.edges_iter(data=True) if e[2]["is_strong"])
strong_deps.add_edges_from(strong_edges)
return strong_deps
请注意construct_task_graph
效率有点低:任务可能是在早期迭代中处理的任务的祖先,产生重复的要求。这些被集合抛出,但更有效的方法是实现类似DFS的东西。
无论如何,我们得到:
>>> task_graph = construct_task_graph(deps, ["a", "d"])
>>> task_graph.nodes()
['a', 'c', 'e', 'd']
>>> task_graph.edges()
[('c', 'a'), ('e', 'c'), ('d', 'c')]
最重要的是:
>>> nx.topological_sort(task_graph)
['d', 'e', 'c', 'a']