我正在使用快速延迟函数,并且在函数上使用@dask.delayed
装饰器时,我已经熟悉了要做什么。我意识到有时候尽管我认为我遵循了最佳实践,但仍需要两次致电compute()
才能获得结果。即不要在另一个延迟延迟函数中调用延迟延迟函数。
我已经在两种情况下遇到了这个问题:当存在嵌套函数时,以及在使用作为延迟对象的类成员的类中调用成员函数时。
@dask.delayed
def add(a, b):
return a + b
def inc(a):
return add(a, 1)
@dask.delayed
def foo(x):
return inc(x)
x = foo(3)
x.compute()
class Add():
def __init__(self, a, b):
self.a = a
self.b = b
@dask.delayed
def calc(self):
return self.a+self.b
a = dask.delayed(1)
b = dask.delayed(2)
add = Add(a, b)
add.calc().compute()
在第一个示例中,x.compute()
不返回结果,而是另一个延迟的对象,我将不得不调用x.compute().compute()
以获得实际结果。但是我相信inc不是延迟函数,因此不违反不在另一个延迟函数中调用延迟函数的规则吗?
在第二个示例中,我再次必须调用add.calc().compute().compute()
以获得实际结果。在这种情况下,self.a
和self.b
只是延迟属性,并且在任何地方都没有嵌套的延迟函数。
有人能帮助我理解为什么在这两种情况下我需要两次致电compute()
吗?甚至更好的是,有人可以在使用迟钝函数时简要地解释一般的“规则”吗?我阅读了文档,但是在这里找不到太多了。
更新:
@malbert指出,示例需要两次调用compute()
,因为延迟函数涉及延迟结果,因此算作“在另一个延迟函数中调用延迟函数”。但是,为什么类似以下内容仅需要调用一次compute()
?
@dask.delayed
def add(a,b):
return a+b
a = dask.delayed(1)
b = dask.delayed(2)
c = add(a,b)
c.compute()
在此示例中,a
和b
也是延迟结果,并且在延迟函数中使用它们。我的随机猜测将是真正重要的是延迟结果在延迟函数中的位置吗?如果仅将它们作为参数传递进来,那可能就很好了?
答案 0 :(得分:0)
我认为关键在于更准确地了解dask.delayed
的作用。
考虑
my_delayed_function = dask.delayed(my_function)
当my_function
用作装饰器时,dask.delayed
返回函数my_delayed_function
,该函数延迟my_function
的执行。用参数调用my_delayed_function
时
delayed_result = my_delayed_function(arg)
这将返回一个对象,该对象包含有关带有参数my_function
的{{1}}执行的所有必要信息。
致电
arg
触发功能的执行。
现在,对两个延迟结果使用诸如result = delayed_result.compute()
之类的运算符的结果是,返回了一个新的延迟结果,该结果将其输入中包含的两个执行捆绑在一起。在此对象上调用+
会触发此捆绑执行。
到目前为止,一切都很好。现在,在第一个示例中,compute
调用foo
,后者调用一个延迟的函数,该函数返回延迟的结果。因此,计算inc
可以做到这一点,并返回延迟的结果。在此延迟的结果(您的“第二”计算)上调用foo
,然后触发其计算。
在第二个示例中,compute
和a
是延迟的结果。使用b
添加两个延迟结果将返回捆绑+
,a
及其执行的延迟结果。现在,由于b
是延迟函数,因此在获得延迟结果时会返回延迟结果。因此,再次计算将返回一个延迟的对象。
在两种情况下,您都没有完全遵循best practices。具体点
避免在延迟的函数中延迟调用
因为在您的第一个示例中,延迟的calc
在add
中被调用,而inc
中则被调用。因此,您正在延迟foo
内呼叫延迟。在第二个示例中,延迟的foo
正在延迟的calc
和a
上工作,因此,您再次在延迟函数中调用了延迟。
在您的问题中,您说
但是我相信inc不是延迟功能,因此它不是 反对不在另一个函数中调用延迟函数的规则 功能延迟?
我怀疑您可能错误地理解了“在延迟功能内调用延迟”。这是指函数中发生的所有事情,因此是函数的一部分:b
包含对延迟的inc
的调用,因此在add
中调用了delay。
问题更新后添加:将延迟的参数传递给延迟的函数会将延迟的执行捆绑为新的延迟结果。这与“在延迟函数中延迟调用”不同,并且是预期用例的一部分。实际上,我也没有在文档中找到对此的明确解释,但是一个切入点可能是this:foo
用于处理延迟的参数。即使尚不清楚,坚持最佳实践(用这种方式解释)也应该在unpack_collections
的输出方面产生可重复的行为。
坚持“避免在延迟的函数中延迟调用”时,将产生以下代码,并在单次调用compute()
之后返回结果:
第一个示例:
compute
第二个示例:
#@dask.delayed
def add(a, b):
return a + b
def inc(a):
return add(a, 1)
@dask.delayed
def foo(x):
return inc(x)
x = foo(3)
x.compute()