python-greenlet/docs/switching.rst

169 lines
6.0 KiB
ReStructuredText

.. _switching:
==========================================================
Switching Between Greenlets: Passing Objects and Control
==========================================================
.. This is an "explanation" document.
.. currentmodule:: greenlet
Switches between greenlets occur when:
- The method `greenlet.switch` of a greenlet is
called, in which case execution jumps to the greenlet whose ``switch()`` is
called; or
- When the method `greenlet.throw` is used to raise an exception in
the target greenlet, in which case execution jumps to the greenlet
whose ``throw`` was called; or
- When a greenlet dies, in which case execution jumps to the
parent greenlet.
During a switch, an object or an exception is "sent" to the target
greenlet; this can be used as a convenient way to pass information
between greenlets. For example:
.. doctest::
>>> from greenlet import greenlet
>>> def test1(x, y):
... z = gr2.switch(x + y)
... print(z)
>>> def test2(u):
... print(u)
... gr1.switch(42)
>>> gr1 = greenlet(test1)
>>> gr2 = greenlet(test2)
>>> gr1.switch("hello", " world")
hello world
42
This prints "hello world" and 42. Note that the arguments of
``test1()`` and ``test2()`` are not provided when the greenlet is
created, but only the first time someone switches to it.
Here are the precise rules for sending objects around:
``g.switch(*args, **kwargs)``
Switches execution to the greenlet ``g``, sending it the given
arguments. As a special case, if ``g`` did not start yet, then it
will start to run now; ``args`` and ``kwargs`` are passed to the
greenlet's ``run()`` function as its arguments.
Dying greenlet
If a greenlet's ``run()`` finishes, its return value is the object
sent to its parent. If ``run()`` terminates with an exception, the
exception is propagated to its parent (unless it is a
``greenlet.GreenletExit`` exception, in which case the exception
object is caught and *returned* to the parent).
Apart from the cases described above, the target greenlet normally
receives the object as the return value of the call to ``switch()`` in
which it was previously suspended. Indeed, although a call to
``switch()`` does not return immediately, it will still return at some
point in the future, when some other greenlet switches back. When this
occurs, then execution resumes just after the ``switch()`` where it was
suspended, and the ``switch()`` itself appears to return the object that
was just sent. This means that ``x = g.switch(y)`` will send the object
``y`` to ``g``, and will later put the (unrelated) object that some
(unrelated) greenlet passes back to us into ``x``.
Multiple And Keyword Arguments
==============================
You can pass multiple or keyword arguments to ``switch()``. If the
greenlet hasn't begun running, those are passed as function arguments
to ``run`` as usual in Python. If the greenlet *was* running, multiple
arguments will be a :class:`tuple`, and keyword arguments will be a
:class:`dict`; any number of positional arguments with keyword
arguments will have the entire set in a tuple, with positional
arguments in their own nested tuple, and keyword arguments as a `dict`
in the the last element of the tuple:
.. doctest::
>>> def test1(x, y, **kwargs):
... while 1:
... z = gr2.switch(x + y + ' ' + str(kwargs))
... if not z: break
... print(z)
>>> def test2(u):
... print(u)
... # A single argument -> itself
... gr1.switch(42)
... # Multiple positional args -> a tuple
... gr1.switch("how", "are", "you")
... # Only keyword arguments -> a dict
... gr1.switch(language='en')
... # one positional and keywords -> ((tuple,), dict)
... gr1.switch("howdy", language='en_US')
... # multiple positional and keywords -> ((tuple,), dict)
... gr1.switch("all", "y'all", language='en_US_OK')
... gr1.switch(None) # terminate
>>> gr1 = greenlet(test1)
>>> gr2 = greenlet(test2)
>>> gr1.switch("hello", " world", language='en')
hello world {'language': 'en'}
42
('how', 'are', 'you')
{'language': 'en'}
(('howdy',), {'language': 'en_US'})
(('all', "y'all"), {'language': 'en_US_OK'})
.. _switch_to_dead:
Switching To Dead Greenlets
===========================
Note that any attempt to switch to a dead greenlet actually goes to the
dead greenlet's parent, or its parent's parent, and so on. (The final
parent is the "main" greenlet, which is never dead.)
.. doctest::
>>> def inner():
... print("Entering inner.")
... print("Returning from inner.")
... return 42
>>> def outer():
... print("Entering outer and spawning inner.")
... inner_glet = greenlet(inner)
... print("Switching to inner.")
... result = inner_glet.switch()
... print("Got from inner value: %s" % (result,))
... print("Switching to inner again.")
... result = inner_glet.switch()
... print("Got from inner value: %s" % (result,))
... return inner_glet
>>> outer_glet = greenlet(outer)
Here, our main greenlet has created another greenlet (``outer``), which in turn
creates a greenlet (``inner``). The outer greenlet switches to the
inner greenlet, which immediately finishes and dies; the outer greenlet
attempts to switch back to the inner greenlet, but since the inner
greenlet is dead, it just switches...to itself (since it was the
parent). Note how the second switch (to the dead greenlet) returns an
empty tuple.
.. doctest::
>>> inner_glet = outer_glet.switch()
Entering outer and spawning inner.
Switching to inner.
Entering inner.
Returning from inner.
Got from inner value: 42
Switching to inner again.
Got from inner value: ()
We can similarly ask the main greenlet to switch to the (dead) inner
greenlet and its (dead) parent and wind up still in the main greenlet.
>>> inner_glet.switch()
()