Как привязать асинхронный метод к нажатию клавиши в 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 2

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.