[Python-ideas] How the heck does async/await work in Python 3.5

Terry Reedy tjreedy at udel.edu
Sat Feb 27 03:08:53 EST 2016


On 2/26/2016 5:12 PM, Maxime S wrote:
>
> 2016-02-25 17:47 GMT+01:00 Guido van Rossum
> <guido at python.org
> <mailto:guido at python.org>>:
>
>     When you're implementing this API on top of tkinter, you'll probably
>     find that you'll have to use tkinter's way of sleeping anyways, so the
>     implementation of waiting in BaseEventLoop using a selector is not
>     useful for this scenario.
>
>     There are probably some possible refactorings in the asyncio package
>     to help you reuse a little more code, but all in all I still think it
>     would be very useful to have an asyncio loop integrated with Tkinter.
>     (Of course Tkinter does support network I/O, so it would be possible
>     to integrate with that, too. Or some hybrid where you somehow figure
>     out how to wait using a Selector *or* tkinter events in the same
>     loop.)
>
>
> It is actually quite easy to implement an asyncio loop over tkinter once
> you realise that tkapp.dooneevent() is very similar to poll(), and

It took me awhile to understand what you mean by 'tkapp'.  Instances of 
tkinter.Tk get an undocumented .tk attribute that is an instance of the 
undocumented and hidden _tkinter class that is called 'tkapp' in printed 
representations.  In other words, after

import tkinter as tk
root = tk.Tk()
tkapp = root.tk

tkapp has a dooneevent method.  I found the tcl doc for it at
https://www.tcl.tk/man/tcl/TclLib/DoOneEvent.htm
Does calling it with DONT_WAIT "TCL_DONT_WAIT - Do not sleep: process 
only events that are ready at the time of the call." differ from calling 
root.update?

     def update(self):
         """Enter event loop until all pending events have been 
processed by Tcl."""
         self.tk.call('update')

If so, how?  I interpret 'pending events' as 'events that are ready'.

To add to the confusion, 2.7 aliased the tkapp methods (and attributes?) 
as Tkinter.tkinter functions, where Tkinter.tkinter was the C-coded 
_tkinter module.  At least some people used the alieases.  In 3.0, the 
aliases were removed, and when 'Tkinter' became 'tkinter', 'tkinter 
because '_tkinter'.  (You may know all this, but I want it recorded and 
possibly added to the docs someday.)

> tkapp.createfilehandler() is very similar to register() (which is not
> that surprising since tcl use poll() internally if available).

As you note below, tkapp.createfilehandler is unix-only and does not 
exist on Windows in 2.x or 3.x.  In 2.7, however, the 
Tkinter.tkinter.createfilehandle alias did exist on Windows, but with 
with a value of None.

> Thus it is possible to create a tk selector and reuse all the code in
> SelectorEventLoop. Unfortunately createfilehandler() is only available
> on UNIX, so some form of threading is probably innevitable on Windows.
>
> class TkSelector(selectors._BaseSelectorImpl):
>      """Selector based on the Tk event loop."""
>
>      def __init__(self, app):
>          super().__init__()
>          self.app = app
>          self.ready = []
>          self.is_timeout = False
>          self.after_key = None
>      def _file_cb(self, fileobj, mask):
>          fd = self._fileobj_lookup(fileobj)
>          self.ready.append((fd, mask))
>      def _timeout_cb(self):
>          self.is_timeout = True
>      def _reset_state(self):
>          del self.ready[:]
>          self.is_timeout = False
>          if self.after_key:
>              self.app.after_cancel(self.after_key)
>              self.after_key = None
>
>      def register(self, fileobj, events, data=None):
>          key = super().register(fileobj, events, data)
>          flags = 0
>          if events & EVENT_READ:
>              flags |= tkinter.READABLE
>          if events & EVENT_WRITE:
>              flags |= tkinter.WRITABLE
>          self.app.createfilehandler(fileobj, flags, self._file_cb)
>          return key
>
>      def unregister(self, fileobj):
>          key = super().unregister(fileobj)
>          self.app.deletefilehandler(fileobj)
>          return key
>
>      def select(self, timeout=None):
>          if timeout is None:
>              pass
>          elif timeout <= 0:
>              while self.app.dooneevent(_tkinter.DONT_WAIT):
>                  pass
>              self.is_timeout = True
>          else:
>              self.after_key = self.app.after(math.ceil(timeout*1000),
> self._timeout_cb)
>          while not (self.is_timeout or self.ready):
>              self.app.dooneevent()
>          ret = []
>          for fd, mask in self.ready:
>              events = 0
>              if mask & tkinter.WRITABLE:
>                  events |= EVENT_WRITE
>              if mask & tkinter.READABLE:
>                  events |= EVENT_READ
>
>              key = self._key_from_fd(fd)
>              if key:
>                  ret.append((key, events & key.events))
>          self._reset_state()
>          return ret
>
> class TkEventLoop(asyncio.SelectorEventLoop):
>      """Asyncio-compatible tkinter event loop."""
>      def __init__(self, app):
>          selector = TkSelector(app)
>          super().__init__(selector=selector)

I am saving this for future reference.

-- 
Terry Jan Reedy



More information about the Python-ideas mailing list