Python Luigi:如何将不同的参数传递给叶任务?
这是我第二次尝试理解如何在Luigi中将参数传递给依赖项。第一个是 想法是:我有Python Luigi:如何将不同的参数传递给叶任务?,python,etl,pipeline,luigi,Python,Etl,Pipeline,Luigi,这是我第二次尝试理解如何在Luigi中将参数传递给依赖项。第一个是 想法是:我有TaskC,它依赖于TaskB,它依赖于TaskA,它依赖于Task0。我希望整个序列始终完全相同,只是我希望能够控制从哪个文件Task0读取,让我们称之为path。Luigi的理念通常是,每个任务应该只知道它所依赖的任务及其参数。问题是TaskC、TaskB和TaskA都必须接受变量path,唯一目的是将其传递给Task0 因此,Luigi为此提供的解决方案称为 下面是一些示例代码: from pathlib im
TaskC
,它依赖于TaskB
,它依赖于TaskA
,它依赖于Task0
。我希望整个序列始终完全相同,只是我希望能够控制从哪个文件Task0
读取,让我们称之为path
。Luigi的理念通常是,每个任务应该只知道它所依赖的任务及其参数。问题是TaskC
、TaskB
和TaskA
都必须接受变量path
,唯一目的是将其传递给Task0
因此,Luigi为此提供的解决方案称为
下面是一些示例代码:
from pathlib import Path
import luigi
from luigi import Task, TaskParameter, IntParameter, LocalTarget, Parameter
class config(luigi.Config):
path = Parameter(default="defaultpath.txt")
class Task0(Task):
path = Parameter(default=config.path)
arg = IntParameter(default=0)
def run(self):
print(f"READING FROM {self.path}")
Path(self.output().path).touch()
def output(self): return LocalTarget(f"task0{self.arg}.txt")
class TaskA(Task):
arg = IntParameter(default=0)
def requires(self): return Task0(arg=self.arg)
def run(self): Path(self.output().path).touch()
def output(self): return LocalTarget(f"taskA{self.arg}.txt")
class TaskB(Task):
arg = IntParameter(default=0)
def requires(self): return TaskA(arg=self.arg)
def run(self): Path(self.output().path).touch()
def output(self): return LocalTarget(f"taskB{self.arg}.txt")
class TaskC(Task):
arg = IntParameter(default=0)
def requires(self): return TaskB(arg=self.arg)
def run(self): Path(self.output().path).touch()
def output(self): return LocalTarget(f"taskC{self.arg}.txt")
(忽略所有输出
和运行
内容。它们就在那里,因此示例可以成功运行。)
上面示例的要点是控制行print(f“READING FROM{self.path}”)
,而不让任务A、B、C依赖于path
实际上,通过配置类,我可以控制Task0
参数。如果未向Task0
传递path
参数,它将采用默认值,即config().path
我现在的问题是,在我看来,这只在“构建时”(解释器第一次加载代码时)起作用,而不是在运行时(细节我不清楚)
因此,这两种方法都不起作用:
(A)
我不知道为什么这不起作用
(B)
这实际上是有道理的。有两个config
类,我只设法更改了其中一个的路径
帮忙
编辑:当然,让path
引用一个全局变量是可行的,但它不是通常意义上的参数
编辑2:我试过下面答案的第1点:
config
具有相同的定义
class config(luigi.Config):
path = Parameter(default="defaultpath.txt")
我修复了指出的错误,即任务0现在是:
class Task0(Task):
path = Parameter(default=config().path)
arg = IntParameter(default=0)
def run(self):
print(f"READING FROM {self.path}")
Path(self.output().path).touch()
def output(self): return LocalTarget(f"task0{self.arg}.txt")
最后我做到了:
if __name__ == "__main__":
for i in range(3):
config.path = Parameter(f"file_{i}")
luigi.build([TaskC(arg=i)], log_level="WARNING")
这不起作用,Task0
仍然会得到path=“defaultpath.txt”
,因此您尝试使用参数创建任务,而不将这些参数传递给父类。这是完全可以理解的,在试图处理这件事时,我有时会感到恼火
首先,您错误地使用了config
类。使用配置类时,如中所述,需要实例化对象。因此,不是:
class Task0(Task):
path = Parameter(default=config.path)
...
您可以使用:
class Task0(Task):
path = Parameter(default=config().path)
...
虽然现在可以确保使用的是值而不是参数
对象,但它仍然不能解决问题。创建类Task0
时,将计算config().path
,因此它不会将config().path
的引用分配给path
,而是在调用时分配值(该值将始终为defaultpath.txt
)。以正确的方式使用该类时,luigi将构造一个Task
对象,其中只有luigi.Parameter
属性作为新实例上的属性名称,如下所示:
因此,我看到了两条可能的前进道路
1.)第一种方法是在运行时像以前一样设置配置路径,只是将其设置为参数对象,如下所示:
config.path = luigi.Parameter(f"newpath_{i}")
但是,使用config.path
让您的任务工作起来需要做很多工作,因为现在它们需要以不同的方式接受其参数(在创建类时无法计算默认值)
2.)更简单的方法是在配置文件中简单地指定类的参数。如果你看一下,你会发现Luigi中的Config
类实际上只是一个Task
类,所以你可以用它做任何事情,你可以用类做任何事情,反之亦然。因此,您可以在配置文件中包含以下内容:
[Task0]
path = newpath_1
...
3.)但是,由于您似乎希望运行多个任务,每个任务都有不同的参数,因此我建议您按照Luigi的建议,将args传递给家长。然后,您可以使用以下工具运行所有内容:
luigi.build([TaskC(arg=i) for i in range(3)])
4.)最后,如果确实需要消除传递的依赖项,可以创建一个ParamaterizedTaskParameter
,该参数扩展luigi.ObjectParameter
,并使用任务实例的pickle作为对象
在上述解决方案中,我强烈建议2个或3个。1将很难编程,4将创建一些非常难看的参数,并且更高级
编辑:解决方案1和解决方案2比任何东西都要复杂,建议您将参数捆绑在DictParameter
2)中,就我所知,它不会在循环中工作,对吧?。3) arg
是我的第二个问题,它只是一个例子。真正的问题是将path
传递给leaf任务。1) 我真的不明白。在您的示例中,配置仍然是一项任务吗?那么,我是不是不应该执行config().path
实例化?2在循环中不起作用是正确的。在3中,我一般使用arg。这可能是你的道路。关键是,您将循环该参数的所有可能值,并使用每个可能值创建一个TaskC,并传入该参数。您不应该执行config().path
实例化。它违背了Luigi的工作方式,不适用于您的用例。好吧,但我的观点是,我不希望所有任务都知道必须传递给Task0
的路径。如果我有100个任务呢?我是否必须向每个人添加他们不依赖的内容,以便他们可以将其传递给下一个?这似乎是一个巨大的设计缺陷。必须有一个解决方案,这不是一个模糊的要求,对吗?顺便说一句,我还编辑了原始问题,其中我描述了我无法让选项1)工作(或
[Task0]
path = newpath_1
...
luigi.build([TaskC(arg=i) for i in range(3)])