我正试图翻译这个键" debouncing"从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
我不想翻译JS超时功能,我宁愿使用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
,但我并不清楚如何使用它们。您将如何使用Python的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
调用"重叠"?
答案 0 :(得分:1)
基本上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(因为没有返回语句)。None
将返回主页。yield from handle_key('b')
并开始处理新密钥。b
)。 main
coroutinr替换为:
@asyncio.coroutine
def main(loop=asyncio.get_event_loop()):
a_task = loop.create_task(handle_key('a'))
b_task = loop.create_task(handle_key('b'))
yield from asyncio.gather(a_task, b_task)
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个协程,它可以启动,但它会选择哪一个?嗯......谁知道它是实施依赖的。因此,为了更好地模拟按下的键,这个替换应该更好一点:
@asyncio.coroutine
def main(loop=asyncio.get_event_loop()):
a_task = loop.create_task(handle_key('a'))
yield from asyncio.sleep(0.1)
b_task = loop.create_task(handle_key('b'))
yield from asyncio.gather(a_task, b_task)
来自文档:
与asyncio一起使用的协同程序可以使用async def语句实现。
在Python 3.5中添加了异步def类型的协同程序,如果不需要支持旧的Python版本,建议使用它。
这意味着您可以替换:
@asyncio.coroutine
def main():
更新的声明
async def main():
如果您开始使用新语法,则必须将yield from
替换为await
。
答案 1 :(得分:1)
两个handle_key
javascript函数都不会阻止执行。每个只是清除超时回调并设置新的。它会立即发生。
协同程序以另一种方式工作:在协程上使用yield from
或newer syntax await
意味着我们只有在完成此协程后才能恢复执行流程:
async def a():
await asyncio.sleep(1)
async def main():
await a()
await b() # this line would be reached only after a() done - after 1 second delay
代码中的 asyncio.sleep(0.5)
- 不是按超时设置回调,而是应该在handle_key
finsihed之前完成的代码。
您可以创建task以开始执行某些协程"在后台"。如果您不希望它完成,您还可以cancel task(就像使用clearTimeout(this.timeout)
一样)。
模拟javascript片段的Python版本:
import asyncio
from contextlib import suppress
global_state = ''
timeout = None
async def handle_key(key):
global global_state, timeout
global_state += key
# cancel previous callback (clearTimeout(this.timeout))
if timeout:
timeout.cancel()
with suppress(asyncio.CancelledError):
await timeout
# set new callback (this.timeout = setTimeout ...)
async def callback():
await asyncio.sleep(0.5)
print(global_state)
timeout = asyncio.ensure_future(callback())
async def main():
await handle_key('a')
await handle_key('b')
# both handle_key functions done, but task isn't finished yet
# you need to await for task before exit main() coroutine and close loop
if timeout:
await timeout
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.close()
虽然上面的代码有效,但不应该使用asyncio
。您的javascript代码基于回调,而asyncio
通常即将避免使用回调。
很难在您的示例中展示差异,因为它本质上是基于回调(键处理 - 是某种全局回调)并且没有更多的异步逻辑。但是,当您添加更多异步操作时,这种理解将非常重要。
现在我建议你阅读现代javascript中的async
/ await
(它类似于Python async
/ await
)并查看将其与回调/承诺进行比较的示例。 This article看起来不错。
它将帮助您了解如何在Python中使用基于协程的方法。
<强> UPD:强>
由于buttons.check
需要定期致电driver.get_buttons()
,您必须使用循环。但它可以作为任务与事件循环一起完成。
如果您有某种button_handler(callback)
(这通常是不同的lib允许处理用户输入的方式),您可以使用它来直接设置一些asyncio.Future
并避免循环。
考虑可能从一开始就用asyncio
写一些小gui应用程序。我认为它可以帮助您更好地了解如何调整现有项目。
这里有一些伪代码,显示了要处理的后台任务 按钮和使用asyncio来处理一些简单的UI事件/状态逻辑:
import asyncio
from contextlib import suppress
# GUI logic:
async def main():
while True:
print('We at main window, popup closed')
key = await key_pressed
if key == 'Enter':
print('Enter - open some popup')
await popup()
# this place wouldn't be reached until popup is not closed
print('Popup was closed')
elif key == 'Esc':
print('Esc - exit program')
return
async def popup():
while True:
key = await key_pressed
if key == 'Esc':
print('Esc inside popup, let us close it')
return
else:
print('Non escape key inside popup, play sound')
# Event loop logic:
async def button_check():
# Where 'key_pressed' is some global asyncio.Future
# that can be used by your coroutines to know some key is pressed
while True:
global key_pressed
for key in get_buttons():
key_pressed.set_result(key)
key_pressed = asyncio.Future()
await asyncio.sleep(0.01)
def run_my_loop(coro):
loop = asyncio.get_event_loop()
# Run background task to process input
buttons_task = asyncio.ensure_future(button_check())
try:
loop.run_until_complete(main())
finally:
# Shutdown task
buttons_task.cancel()
with suppress(asyncio.CancelledError):
loop.run_until_complete(buttons_task)
loop.close()
if __name__ == '__main__':
run_my_loop(main())