python 源码阅读 - generator and coroutine

Python generator and coroutine

# ./lib/python3.6/asyncio/tasks.py
"""
## PEP 342
## 1. Calling send(None) is exactly equivalent to calling a generator's
## next() method.
## 2. As with the next() method, the send() method returns the
## next value yielded by the generator-iterator, or raises
## StopIteration if the generator exits normally, or has already exited.
"""

class Task(futures.Future):
    """Coroutines will be wrapped in Tasks."""
    ...

    def _step(self, exc=None):
        ...
        coro = self._coro
        self._fut_waiter = None

        self.__class__._current_tasks[self._loop] = self
        # Call either coro.throw(exc) or coro.send(None).
        try:
            if exc is None:
                # We use the `send` method directly, because coroutines
                # don't have `__iter__` and `__next__` methods.
                ## PEP 342
                ## 1. Calling send(None) is exactly equivalent to calling a generator's
                ## next() method.
                ## 2. As with the next() method, the send() method returns the
                ## next value yielded by the generator-iterator, or raises
                ## StopIteration if the generator exits normally, or has already exited.
                result = coro.send(None)
            else:
                result = coro.throw(exc)
        except StopIteration as exc:
            if self._must_cancel:
                # Task is cancelled right before coro stops.
                self._must_cancel = False
                self.set_exception(futures.CancelledError())
            else:
                self.set_result(exc.value)
        except futures.CancelledError:
            # I.e., Future.cancel(self).
            super().cancel()
        except Exception as exc:
            self.set_exception(exc)
        except BaseException as exc:
            self.set_exception(exc)
            raise
        else:
            blocking = getattr(result, '_asyncio_future_blocking', None)
            if blocking is not None:
                # Yielded Future must come from Future.__iter__().
                if result._loop is not self._loop:
                    self._loop.call_soon(
                        self._step,
                        RuntimeError(
                            'Task {!r} got Future {!r} attached to a '
                            'different loop'.format(self, result)))
                elif blocking:
                    if result is self:
                        self._loop.call_soon(
                            self._step,
                            RuntimeError(
                                'Task cannot await on itself: {!r}'.format(
                                    self)))
                    else:
                        result._asyncio_future_blocking = False
                        result.add_done_callback(self._wakeup)
                        self._fut_waiter = result
                        if self._must_cancel:
                            if self._fut_waiter.cancel():
                                self._must_cancel = False
                else:
                    self._loop.call_soon(
                        self._step,
                        RuntimeError(
                            'yield was used instead of yield from '
                            'in task {!r} with {!r}'.format(self, result)))
            elif result is None:
                # Bare yield relinquishes control for one event loop iteration.
                self._loop.call_soon(self._step)
            elif inspect.isgenerator(result):
                # Yielding a generator is just wrong.
                self._loop.call_soon(
                    self._step,
                    RuntimeError(
                        'yield was used instead of yield from for '
                        'generator in task {!r} with {}'.format(
                            self, result)))
            else:
                # Yielding something else is an error.
                self._loop.call_soon(
                    self._step,
                    RuntimeError(
                        'Task got bad yield: {!r}'.format(result)))
        finally:
            self.__class__._current_tasks.pop(self._loop)
            self = None  # Needed to break cycles when an exception occurs.
     ...

Coroutines

AKA: abstract of subroutines (can be entered, exited, and resumed at many different points)

Coroutines is a more generalized form of subroutines. Subroutines are entered at one point and exited at another point. Coroutines can be entered, exited, and resumed at many different points. They can be implemented with the async def statement. See also PEP 492.

coroutine function

AKA: async def function

A function which returns a coroutine object. A coroutine function may be defined with the async def statement, and may contain await, asyncfor, and async with keywords. These were introduced by PEP 492.

generator iterator

AKA: generator function call return generator iterator

An object created by a generator function.

Each yield temporarily suspends processing, remembering the location execution state (including local variables and pending try-statements). When the generator iterator resumes, it picks-up where it left-off (in contrast to functions which start fresh on every invocation).

generator

AKA: function contains yield (which return generator iterator)

A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.

Usually refers to a generator function, but may refer to a generator iterator in some contexts. In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.

# example
import inspect

def example():
    for i in range(10):
        yield i

inspect.isgeneratorfunction(example) # True
inspect.isgenerator(example())       # True
inspect.isgenerator(example)         # False
-->