重用Twisted中的延迟对象 [英] Re-using deferred objects in Twisted
问题描述
在Twisted中,似乎延迟的对象在其回调触发后只能使用一次,这与我使用过的其他基于承诺的库相反:
来自twisted.internet导入延迟
类Foo(对象):
def __init __(self) :
self.dfd = defer.Deferred()
def async(self):
return self.dfd
@ defer.inlineCallbacks
def func(self):
打印'Started!'
结果= yield self.async()
打印'结果停止:{0} '.format(结果)
如果__name__ =='__main__':
foo = Foo()
foo.func()
导入时间
time.sleep(3)
foo.dfd.callback('3秒过去了!')
foo.func()
在标准输出下,一个具有:
$开始了!
$结果停止:3秒过去了!
$开始!
$停止结果:无
在我的情况下,我希望 func
在 reactor
线程中一次又一次地被调用。有什么方法可以确保 yield
调用始终返回延迟对象的已解析值而不会引入额外状态,如果这样,最优雅的方法是这样做吗?
更新
根据以下建议,我实现了作为装饰者的解决方案:
导入functools
def recycles_deferred(deferred_getter):
给出一个返回延迟对象的可调用deferred_getter,创建
另一个函数,该函数返回该延迟对象的'可重用'版本。
@ functools.wraps(deferred_getter)
def _recycler(* args,** kwargs):
old_dfd = deferred_getter(* args,** kwargs)
new_dfd = defer.Deferred()
def _recycle (结果):
new_dfd.callback(结果)
返回结果
old_dfd.addCallback(_recycle)
返回new_dfd
重新如果__name__ =='__main__',则转_recycler
:
演示如何使用此@recycles_deferred。
从twisted.internet导入时间
延迟
O类(对象):
def __init __(self):
在实践中,这可以表示一个网络请求。
self.dfd = defer.Deferred()
def do_something_with_result(self,result):
print'得到结果:{0}'。format(结果)
返回结果
@recycles_deferred
def deferred_getter(自己):
返回延期。
return self.dfd
@ defer.inlineCallbacks
def do_something_with_deferred(self):
结果= yield self.deferred_getter()
打印'得到内联结果:{0}'。format(结果)
o = O()
o.dfd.addCallback(o.do_som ething_with_result)#得到结果:foo
o.do_something_with_deferred()#得到内联结果:foo
o.dfd.addCallback(o.do_something_with_result)#得到结果:foo
#睡眠3秒,然后解决延迟的
时间.sleep(3)
o.dfd.callback('foo')
o.do_something_with_deferred()#获得内联结果: foo
o.dfd.addCallback(o.do_something_with_result)#得到结果:foo
#对yield的内联调用永远不会返回None
o.do_something_with_deferred()#得到内联结果:foo
o.do_something_with_deferred()#获得内联结果:foo
o.do_something_with_deferred()#获得内联结果:foo
问题不在于 Deferred
本身只能使用一次-它可以无限重复使用,从某种意义上说,您可以一直向其添加回调,并且数据将继续流向下一个callbac。 k,以及下一个(如果有)。您所看到的问题是,当您将回调添加到 Deferred
时,其结果将传播到下一个回调。
另一个相交的问题是,收益
将推迟
假定 inlineCallbacks
函数消耗了 Deferred
-您正在获得它的价值并为此做点事情,因此,为避免不必要的资源利用( Deferred
携带结果的时间长于所需时间),回调将为您提供 yield的结果
表达式本身也会返回 None
。我想,如果它返回某种更明确的由 inlineCallbacks
令牌消耗的东西,可能会更容易理解,但事后看来是20/20 :-)。 / p>
但是从某种意义上说,推迟
只能使用一次,也就是说,如果您有一个返回一个 Deferred
的API,它应该返回一个 new 推迟
给每个呼叫者。归还它,实际上是将所有权转移给了呼叫者,因为呼叫者可以修改结果,以将其传递给自己的呼叫者。典型的示例是,如果您有一个返回 Deferred
并以某些字节触发的API,但是您知道这些字节应该是JSON,则可以添加 .addCallback(json.loads)
然后返回它,这将允许调用方使用JSON序列化的对象而不是字节。
因此,如果您打算多次调用 async
,则您将采用以下方式:
$ b来自__future__的$ b
导入print_function,unicode_literals
从twisted.internet导入推迟
类Foo(object):
def __init __(self):
self.dfd = defer.Deferred()
def async(self):
justForThisCall = defer.Deferred()
def callbackForDFD(结果):
justForThisCall.callback(结果)
返回结果
self.dfd.addCallback(callbackForDFD)
return justForThisCall
@def er.inlineCallbacks
def func(self):
print('Started!')
结果= yield self.async()
print('结果停止:{0} '.format(result))
如果__name__ =='__main__':
foo = Foo()
print( calling func)
foo.func ()
print( firing dfd)
foo.dfd.callback('无需等待!')
print(再次调用func)
foo.func ()
print( done)
应该会产生以下输出:
调用func
已开始!
fireing dfd
结果停止:无需等待!
再次调用func
开始了!
结果停止:无需等待!
完成
In Twisted, it seems that a deferred object can only be used once after its callback has fired, as opposed to other "promise"-based libraries I've worked with:
from twisted.internet import defer
class Foo(object):
def __init__(self):
self.dfd = defer.Deferred()
def async(self):
return self.dfd
@defer.inlineCallbacks
def func(self):
print 'Started!'
result = yield self.async()
print 'Stopped with result: {0}'.format(result)
if __name__ == '__main__':
foo = Foo()
foo.func()
import time
time.sleep(3)
foo.dfd.callback('3 seconds passed!')
foo.func()
On standard out, one has:
$ Started!
$ Stopped with result: 3 seconds passed!
$ Started!
$ Stopped with result: None
In my situation, I'm expecting to func
to be called again and again within the reactor
thread. Is there any way to ensure that the yield
call will always return the "resolved" value of the deferred object without introducing extra state, and if so, what's the most elegant way to do so?
UPDATE
Based on the advice below, I implemented a solution as a decorator:
import functools
def recycles_deferred(deferred_getter):
"""Given a callable deferred_getter that returns a deferred object, create
another function that returns a 'reusable' version of that deferred object."""
@functools.wraps(deferred_getter)
def _recycler(*args, **kwargs):
old_dfd = deferred_getter(*args, **kwargs)
new_dfd = defer.Deferred()
def _recycle(result):
new_dfd.callback(result)
return result
old_dfd.addCallback(_recycle)
return new_dfd
return _recycler
if __name__ == '__main__':
"""Demonstration of how this @recycles_deferred should be used."""
import time
from twisted.internet import defer
class O(object):
def __init__(self):
"""In practice this could representation a network request."""
self.dfd = defer.Deferred()
def do_something_with_result(self, result):
print 'Got result: {0}'.format(result)
return result
@recycles_deferred
def deferred_getter(self):
"""Return the deferred."""
return self.dfd
@defer.inlineCallbacks
def do_something_with_deferred(self):
result = yield self.deferred_getter()
print 'Got inline result: {0}'.format(result)
o = O()
o.dfd.addCallback(o.do_something_with_result) # Got result: foo
o.do_something_with_deferred() # Got inline result: foo
o.dfd.addCallback(o.do_something_with_result) # Got result: foo
# sleep 3 seconds, then resolve the deferred
time.sleep(3)
o.dfd.callback('foo')
o.do_something_with_deferred() # Got inline result: foo
o.dfd.addCallback(o.do_something_with_result) # Got result: foo
# the inline call to yield never returns None
o.do_something_with_deferred() # Got inline result: foo
o.do_something_with_deferred() # Got inline result: foo
o.do_something_with_deferred() # Got inline result: foo
The issue is not that the Deferred
itself can only be "used once" - it's infinitely re-usable, in the sense that you can keep adding callbacks to it forever and data will continue flowing to the next callback, and the next, as it's available. The problem you're seeing is that when you add a callback to a Deferred
, its result is propagated to the next callback.
The other, intersecting problem here is that yield
ing a Deferred
from an inlineCallbacks
function is assumed to "consume" a Deferred
- you're getting its value and doing something with it, so to prevent unnecessary resource utilization (that Deferred
carrying around a result for longer than it needs to), the callback that gives you the result from the yield
expression also itself returns None
. It might be a little easier to understand if it returned some kind of more explicit "consumed by inlineCallbacks
token", I suppose, but hindsight is 20/20 :-).
But in a sense, a Deferred
can only be "used" once, which is to say, if you have an API which returns a Deferred
, it should return a new Deferred
to each caller. By returning it, you're really transferring ownership to the caller, because callers may modify the result, to pass on to their own callers. The typical example is that if you have an API that returns a Deferred
that fires with some bytes, but you know the bytes are supposed to be JSON, you might add .addCallback(json.loads)
and then return it, which would allow that caller to consume the JSON-serialized object rather than the bytes.
So if you intend for async
to be called multiple times, the way you would do it is something like this:
from __future__ import print_function, unicode_literals
from twisted.internet import defer
class Foo(object):
def __init__(self):
self.dfd = defer.Deferred()
def async(self):
justForThisCall = defer.Deferred()
def callbackForDFD(result):
justForThisCall.callback(result)
return result
self.dfd.addCallback(callbackForDFD)
return justForThisCall
@defer.inlineCallbacks
def func(self):
print('Started!')
result = yield self.async()
print('Stopped with result: {0}'.format(result))
if __name__ == '__main__':
foo = Foo()
print("calling func")
foo.func()
print("firing dfd")
foo.dfd.callback('no need to wait!')
print("calling func again")
foo.func()
print("done")
which should produce this output:
calling func
Started!
firing dfd
Stopped with result: no need to wait!
calling func again
Started!
Stopped with result: no need to wait!
done
这篇关于重用Twisted中的延迟对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!