Python 如何递归地将返回列表的芹菜任务链接到一个组中?

Python 如何递归地将返回列表的芹菜任务链接到一个组中?,python,celery,Python,Celery,我从这个问题开始: 但我想扩大两倍。因此,在我的用例中,我有: 任务A:确定给定日期的项目总数 任务B:下载该日期的1000个元数据条目 任务C:下载一个项目的内容 所以每一步我都在扩展下一步的项目数量。我可以通过循环我的任务中的结果并在下一个任务函数中调用.delay()来实现这一点。但我想我不会让我的主要任务那样做。相反,它们将返回一个元组列表——然后将每个元组扩展为用于调用下一个函数的参数 上述问题的答案似乎满足了我的需要,但我无法找到将其链接为两级扩展的正确方法 下面是我的代码的一个

我从这个问题开始:

但我想扩大两倍。因此,在我的用例中,我有:

  • 任务A:确定给定日期的项目总数
  • 任务B:下载该日期的1000个元数据条目
  • 任务C:下载一个项目的内容
所以每一步我都在扩展下一步的项目数量。我可以通过循环我的任务中的结果并在下一个任务函数中调用
.delay()
来实现这一点。但我想我不会让我的主要任务那样做。相反,它们将返回一个元组列表——然后将每个元组扩展为用于调用下一个函数的参数

上述问题的答案似乎满足了我的需要,但我无法找到将其链接为两级扩展的正确方法

下面是我的代码的一个非常精简的示例:

来自芹菜导入组
从celery.task导入子任务
从celery.utils.log导入获取任务日志
来自。芹菜导入应用程序
记录器=获取任务记录器(名称)
@应用程序任务
def任务_范围(上限=10):
#包装在列表中使JSON序列化程序工作
返回列表(zip(范围(上限)、范围(上限)))
@应用程序任务
def添加(x,y):
info(f'x是{x},y是{y}'))
char=chr(ord('a')+x)
char2=chr(ord('a')+x*2)
结果=x+y
info(f'result为{result}')
返回列表(zip(char*result,char2*result))
@应用程序任务
def联合收割机日志(c1、c2):
info(f'combine log是{c1}{c2}')
@应用程序任务
def dmap(参数、芹菜任务):
"""
获取参数元组的迭代器并将其排队,以便芹菜与函数一起运行。
"""
info(在dmap中,len iter:{len(args_iter)}'))
回调=子任务(芹菜任务)
运行\u in_parallel=group(args中args的callback.clone(args))
返回并行运行中的运行延迟()
然后,我尝试了各种方法来实现嵌套映射。首先,一级映射工作正常,因此:

pp=(task_range.s()| dmap.s(add.s())
pp(2)
产生了我所期望的结果,所以我并没有完全放弃

但当我尝试添加另一个级别时:

ppp=(task_range.s()| dmap.s(add.s()| dmap.s(combine_log.s()))
然后在worker中我看到了错误:

[2019-11-23 22:34:12,024: ERROR/ForkPoolWorker-2] Task proj.tasks.dmap[e92877a9-85ce-4f16-88e3-d6889bc27867] raised unexpected: TypeError("add() missing 2 required positional arguments: 'x' and 'y'",)
Traceback (most recent call last):
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/app/trace.py", line 385, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/app/trace.py", line 648, in __protected_call__
    return self.run(*args, **kwargs)
  File "/home/hdowner/dev/playground/celery/proj/tasks.py", line 44, in dmap
    return run_in_parallel.delay()
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/canvas.py", line 186, in delay
    return self.apply_async(partial_args, partial_kwargs)
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/canvas.py", line 1008, in apply_async
    args=args, kwargs=kwargs, **options))
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/canvas.py", line 1092, in _apply_tasks
    **options)
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/canvas.py", line 578, in apply_async
    dict(self.options, **options) if options else self.options))
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/canvas.py", line 607, in run
    first_task.apply_async(**options)
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/canvas.py", line 229, in apply_async
    return _apply(args, kwargs, **options)
  File "/home/hdowner/.venv/play_celery/lib/python3.6/site-packages/celery/app/task.py", line 532, in apply_async
    check_arguments(*(args or ()), **(kwargs or {}))
TypeError: add() missing 2 required positional arguments: 'x' and 'y'

我不知道为什么将参数从普通任务签名更改为链将参数更改为
dmap()
,会改变参数传递到
add()
的方式。我的印象是不应该这样,它只是意味着
add()
的返回值将被传递。但显然情况并非如此…

问题在于
链上的
clone()
方法在某个点上没有传递参数-请参阅以获取完整的详细信息。如果我使用该答案中的方法,我的
dmap()
代码将变为:

@app.task
def dmap(参数、芹菜任务):
"""
获取参数元组的迭代器并将其排队,以便芹菜与函数一起运行。
"""
回调=子任务(芹菜任务)
运行\u in\u parallel=group(args\u iter中args的克隆\u签名(回调,args))
返回并行运行中的运行延迟()
def克隆_签名(sig,args=(),kwargs=(),**选项):
"""
结果表明,链克隆()没有正确复制参数-这是
克隆人可以。
发件人:https://stackoverflow.com/a/53442344/3189
"""
如果sig.subtask_类型和sig.subtask_类型!=“链”:
引发未实现的错误(
“仅支持克隆任务和链,不支持{}”。格式(sig.subtask_类型)
)
clone=sig.clone()
如果hasattr(克隆,“任务”):
task\u to\u apply\u args\u to=clone.tasks[0]
其他:
任务\u至\u应用\u参数\u至=克隆
args,kwargs,opts=task\u to\u apply\u args\u to.\u merge(args=args,kwargs=kwargs,options=opts)
task_to_apply_args_to.update(args=args,kwargs=kwargs,options=deepcopy(opts))
返回克隆
当我这样做的时候:

ppp=(task_range.s()| dmap.s(add.s()| dmap.s(combine_log.s()))

一切正常。

谢谢你的回答。我必须调整代码,以确保它能够处理带有单个参数的任务。我相信这很糟糕,但它确实有效!如有任何改进,我们将不胜感激

@celery_app.task(name='app.worker.dmap')
def dmap(args_iter, celery_task):
    """
    Takes an iterator of argument tuples and queues them up for celery to run with the function.
    """
    callback = subtask(celery_task)
    print(f"ARGS: {args_iter}")
    args_list = []
    run_in_parallel = group(clone_signature(callback, args if type(args) is list else [args]) for args in args_iter)
    print(f"Finished Loops: {run_in_parallel}")
    return run_in_parallel.delay()
具体地说,我补充说:

if type(args) is list else [args]
关于这一行:

run_in_parallel = group(clone_signature(callback, args ***if type(args) is list else [args]***) for args in args_iter)
我们可以像
(task_range.s()| dmap.s(add.s()| dmap.s(combine_log.s())|一些任务.s())