我试图将这个关键的“去抖动”逻辑从Javascript转换为Python .
function handle_key(key) {
if (this.state == null) {
this.state = ''
}
this.state += key
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
console.log(this.state)
}, 500)
}
handle_key('a')
handle_key('b')
这个想法是随后的按键延长超时 . Javascript版本打印:
ab
我不喜欢使用asyncio的惯用Python . 我在Python(3.5)中的尝试如下,但它不起作用,因为 global_state
在我预期时实际上没有更新 .
import asyncio
global_state = ''
@asyncio.coroutine
def handle_key(key):
global global_state
global_state += key
local_state = global_state
yield from asyncio.sleep(0.5)
#if another call hasn't modified global_state we print it
if local_state == global_state:
print(global_state)
@asyncio.coroutine
def main():
yield from handle_key('a')
yield from handle_key('b')
ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(main())
它打印:
a
ab
我已经研究过asyncio Event, Queue and Condition但它不是't clear to me how to use them for this. How would you implement the desired behavior using Python' s asyncio?
编辑
关于我如何使用 handle_keys
的更多细节 . 我有一个异步功能,检查按键 .
@asyncio.coroutine
def check_keys():
keys = driver.get_keys()
for key in keys:
yield from handle_key(key)
而这又与其他程序任务一起安排
@asyncio.coroutine
def main():
while True:
yield from check_keys()
yield from do_other_stuff()
ioloop = asyncio.get_event_loop()
ioloop.run_until_complete(main())
Qeek's use of asyncio.create_task and asyncio.gather是有道理的 . 但是我如何在这样的循环中使用它呢?或者是否有另一种方法来安排允许 handle_keys
调用"overlap"的异步任务?
2 回答
怎么了?
基本上
yield from xy()
与普通函数调用非常相似 . 函数调用和yield from
之间的区别在于函数调用立即开始处理调用函数 .yield from
语句在事件循环内调用coroutine进入队列,并控制事件循环,并决定处理队列中的哪个协同程序 .以下是您的代码所做的解释:
它将
main
添加到事件循环的队列中 .事件循环开始处理队列中的协同程序 .
队列只包含
main
协同程序,因此启动它 .代码命中
yield from handle_key('a')
.它在事件循环的队列中添加
handle_key('a')
.事件循环现在包含
main
和handle_key('a')
但主要无法启动,因为它正在等待handle_key('a')
的结果 .因此事件循环启动
handle_key('a')
.它会做一些东西,直到它击中
yield from asyncio.sleep(0.5)
.现在事件循环包含
main()
,handle_key('a')
和sleep(0.5)
.main()
正在等待handle_key('a')
的结果 .handle_key('a')
正在等待sleep(0.5)
的结果 .睡眠没有依赖性,因此可以启动它 .
asyncio.sleep(0.5)
在0.5秒后返回None
.事件循环采用
None
并将其返回到handle_key('a')
协程 .忽略返回值,因为它未分配给任何内容
handle_key('a')
打印密钥(因为没有任何改变状态)最后的
handle_key
协程返回None(因为没有return语句) .None
返回主页面 .再次忽略返回值 .
代码命中
yield from handle_key('b')
并开始处理新密钥 .它从步骤5开始执行相同的步骤(但使用键
b
) .如何修复它
main
coroutinr替换为:loop.create_task
将handle_key('a')
和handle_key('b')
添加到事件循环的队列中,然后yield from asyncio.gather(a_task, b_task)
将控制权添加到事件循环中 . 此点的事件循环包含handle_key('a')
,handle_key('b')
,gather(...)
和main()
.main()
对gather()
的结果感到怀疑gather()
等待所有作为参数给出的任务完成handle_key('a')
和handle_key('b')
没有依赖关系,因此可以启动它们 .事件循环现在包含2个协程,它可以启动,但它会选择哪一个?嗯...谁知道它是实施依赖的 . 因此,为了更好地模拟按键,这个替换应该更好一些:
Python 3.5奖金
从文档:
这意味着你可以替换:
更新的声明
如果您开始使用新语法,则还必须将
yield from
替换为await
.为什么你的代码现在不起作用?
两个
handle_key
javascript函数都不会阻止执行 . 每个只是清除超时回调并设置新的 . 它会立即发生 .协同程序以另一种方式工作:在协同程序上使用
yield from
或newer syntaxawait
意味着我们只想在完成此协程之后才恢复执行流程:您的代码中的
asyncio.sleep(0.5)
- 不是通过超时设置回调,而是应该在handle_key
finsihed之前完成的代码 .让我们尝试使代码工作
你可以创建task来开始执行一些协程"in background" . 如果您不希望它完成,您也可以cancel task(就像使用
clearTimeout(this.timeout)
一样) .模拟您的JavaScript代码段的Python版本:
惯用语?
虽然上面的代码有效,但不应该如何使用
asyncio
. 您的javascript代码基于回调,而asyncio
通常即将避免使用回调 .它的很难证明你的例子有什么不同,因为它本质上是回调(键处理 - 是某种全局回调)并且没有更多的异步逻辑 . 但是,当您添加更多异步操作时,这种理解将会很重要 .
现在我建议你在现代javascript('s similar to Python' s
async
/await
)中阅读handle_key
/await
并查看将其与回调/承诺进行比较的示例 . This article看起来不错 .它将帮助您了解如何在Python中使用基于协程的方法 .
Upd:
buttons.check
需要定期调用driver.get_buttons()
,你必须使用循环 . 但它可以作为任务和事件循环一起完成 .如果您有某种
button_handler(callback)
(这通常是不同的库允许处理用户输入的方式),您可以使用它直接设置一些asyncio.Future
并避免循环 .考虑可能从一开始就用
asyncio
编写一些小gui应用程序 . 我认为它可以帮助您更好地了解如何调整现有项目 .这里有一些伪代码,显示处理按钮的后台任务,并使用asyncio来处理一些简单的UI事件/状态逻辑:
.