你的怀疑基本上是正确的。
访问时可能没有结果(引发 AttributeError)
Deferred 的想法是一些工作正在发生,并且不会在某个固定时间完成。完成后就会完成。幸运的是,当它完成时,Deferred 可以告诉你结果——这就是回调的用途。这意味着,没有使用回调,你无法知道工作何时完成。您可能过早检查并收到AttributeError(或另一种失败,更多内容见下文)。您可能检查得太晚并浪费了一些时间。您可以通过反复检查和处理错误来“修复”过早的情况。这称为“轮询”。 Twisted 的大部分存在以消除执行轮询的需要,因为轮询通常是不可取的(它很昂贵)。 “修复”过晚情况的唯一方法是更频繁地检查 - 这会使“过早”情况变得更糟。
访问时结果可能不是最终结果(即并非所有回调都已运行)
Deferred 提供的一个功能是它允许在事件驱动编程中组合。您可以让单元 A 执行某些任务并返回 Deferred。您可以让单元 B 执行某些任务并依赖于单元 A 并返回其他一些 Deferred。这样的组合可能如下所示:
d = Deferred()
d_a = unit_a()
def a_finished(result):
d_b = unit_b(result)
d_b.addCallback(d_final.callback)
d_a.addCallback(a_finished)
d.result 在此示例的执行过程中具有什么价值?首先是AttributeError,然后是unit_b Deferred 被回调的任何值。在这种情况下,您没有暴露不完整或中间结果,也没有问题。但是,这种组合笨拙、冗长且容易失败(例如,它错过了错误传播和取消功能)。
编写Deferred API 的简单、惯用、受鼓励的方式是:
d = unit_a()
d.addCallback(unit_b)
更易于编写、更易于阅读,并且您可以获得铁路式错误传播等功能。
但是,d.result 在这种情况下具有什么价值?首先是AttributeError。那么在unit_a完成后,d.result就是unit_b返回的Deferred。因此,如果您在此处查找,不仅没有最终结果,甚至没有真正的价值,而是有一个Deferred。只有在unit_b 完成后,d 才会接受unit_b 的真实结果。
还有其他可能的顺序。它们来自其他Deferred 使用模式,这些使用模式不如上述模式那么受欢迎,但它们当然是可能的。例如,您可能有一个实现:
d = unit_a()
然后让d 暴露给您的代码。在那之后,您可能会继续执行:
d.addCallback(unit_b)
现在d.result 从AttributeError 到unit_a 的结果到Deferred 到unit_b 的结果。以上不是对Deferred 的出色使用,但正如您所见,这是完全可能的。如果您在这种情况下轮询d.result,您将不得不猜测非Deferred 值是unit_a 还是unit_b 的结果。
是否存在适合访问 Deferred 结果值的情况?
很难完全排除。当然,在Deferred 本身的测试套件中,有一些直接访问,这些可能是合法的。可能在其他一些与测试相关的工具中,以这种方式访问result 属性可能是合适的——但理想情况下,即使在那里也可以避免,或者测试库(例如 Twisted 的试用版)会提供更好的工具来编写这些类型的测试(例如,试用确实提供successResultOf、failureResultOf 和assertNoResult)。除了这些情况之外,我认为您应该非常非常仔细地查看 result 属性的任何使用情况,您可能会发现使用回调有更好(更易于维护、更不脆弱)的解决方案。
有没有更好的方法来访问结果以将其分配给变量或稍后使用它而不向 Deferred 添加额外的回调?
访问结果的更好方法总是添加一个额外的回调。正如另一个答案所暗示的那样,inlineCallbacks 是一种使用回调的方法,它看起来不像使用回调,并且受到许多人的青睐。它允许您对本地进行简单的分配以检索结果,但实现仍然使用Deferred.addCallback 和相关的 API。