1

我正在使用 dask 延迟函数,并且熟悉@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,andself.b只是延迟属性,并且在任何地方都没有嵌套的延迟函数。

谁能帮我理解为什么compute()在这两种情况下我需要打两次电话?或者更好的是,有人可以在使用 dask 延迟函数时简要解释一般“规则”吗?我阅读了文档,在那里找不到太多内容。

更新:@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()

在这个例子中,ab也是延迟的结果,它们用于延迟函数。我的随机猜测实际上重要的是延迟结果在延迟函数中的位置?只有当它们作为参数传入时才可能很好?

4

1 回答 1

1

我认为关键在于更准确地理解是做什么dask.delayed的。

考虑

my_delayed_function = dask.delayed(my_function)

当用作 上的装饰器时my_functiondask.delayed返回一个my_delayed_function延迟执行的函数my_function。何时my_delayed_function使用参数调用

delayed_result = my_delayed_function(arg)

my_function这将返回一个对象,其中包含有关使用参数执行的所有必要信息arg

打电话

result = delayed_result.compute()

触发函数的执行。

现在,+在两个延迟结果上使用运算符的效果是返回一个新的延迟结果,该结果捆绑了其输入中包含的两个执行。调用compute这个对象会触发这一系列的执行。


到目前为止,一切都很好。现在,在您的第一个示例中,foo调用inc调用延迟函数,该函数返回延迟结果。因此,计算foo正是这样做并返回这个延迟的结果。调用compute这个延迟的结果(你的“第二个”计算)然后触发它的计算。

在您的第二个示例中,a并且b是延迟的结果。添加两个延迟结果 using+返回捆绑执行的延迟结果a及其b相加。现在,由于calc是一个延迟函数,它在获得延迟结果时返回延迟结果。因此,它的计算将再次返回一个延迟的对象。

在这两种情况下,您都没有完全遵循最佳实践。具体点

避免在延迟函数中调用延迟

因为在您的第一个示例中,延迟add被称为 within inc,它被称为 in foo。因此,您在延迟内调用延迟foo。在您的第二个示例中,延迟calc正在处理延迟ab因此您再次在延迟函数中调用延迟。

在你的问题中,你说

但我相信 inc 不是延迟函数,因此它不违反不在另一个延迟函数中调用延迟函数的规则?

我怀疑您可能错误地理解了“在延迟函数中调用延迟”。这指的是函数内发生的所有事情,因此是它的一部分:inc包括对延迟的调用add,因此延迟被调用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()

第二个例子:

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()
于 2019-07-10T17:29:31.670 回答