Как привязать асинхронный метод к нажатию клавиши в Tkinter?
Рассмотрим следующий пример:
import asyncio
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.create_widgets()
self._configure_bindings() # I believe it is not possible
# to do this if the method needs
# to be async as well
def create_widgets(self):
pass
def _configure_bindings(self):
self.bind('<F5>', self.spam) # what's the proper way?
# does this method need to be async as well?
async def spam(self, event):
await self.do_something()
async def do_something():
pass
async def run_tk(root):
try:
while True:
root.update()
await asyncio.sleep(.01)
except tk.TclError as e:
if "application has been destroyed" not in e.args[0]:
raise
if __name__ == '__main__':
app = App()
asyncio.get_event_loop().run_until_complete(run_tk(app))
Как правильно привязать асинхронный метод к нажатию клавиши в tkinter? Я попробовал что-то вроде:
self.bind('<F5>', self.spam)
self.bind('<F5>', await self.spam)
self.bind('<F5>', await self.spam())
self.bind('<F5>', lambda event: await self.spam(event))
...и куча других комбинаций, но безрезультатно.
1 ответ:
tkinter
сама асинхронна благодаря событийному циклу, тоafter
Метод и привязки .Однако, если вы пытаетесь придерживаться
asyncio
это тоже возможно, но сначала давайте рассмотрим, что вы пробовали.Ваша первая попытка, очевидно, не удалась, потому что вы пытаетесь вызвать
spam
как универсальную функцию, когда этоcoroutine
. Ваши другие попытки более корректны, чем первая, ноawait coroutine
илиyield from coroutine
можно использовать для запуска сопрограммы из другого только корутин, поэтому он снова терпит неудачу.Итак, правильный способ запуска этого зверя-это планирование его выполнения с помощью самоочевидного метода
ensure_future
(или старыйasync
, что является просто устаревшим псевдонимом).Попробуйте следующий пример:
import asyncio import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self._configure_bindings() def _configure_bindings(self): self.bind('<F5>', lambda event: asyncio.ensure_future(self.spam(event))) async def spam(self, event): await self.do_something() await asyncio.sleep(2) print('%s executed!' % self.spam.__name__) async def do_something(self): print('%s executed!' % self.do_something.__name__) async def run_tk(root): try: while True: root.update() await asyncio.sleep(.01) except tk.TclError as e: if "application has been destroyed" not in e.args[0]: raise if __name__ == '__main__': app = App() asyncio.get_event_loop().run_until_complete(run_tk(app))
Кроме того, я думаю, что стоит упомянуть этот вопрос, так как вы используете метод
update
.