Proper type annotation of Python functions with yield
<p>After reading Eli Bendersky's article <a href="http://eli.thegreenplace.net/2009/08/29/co-routines-as-an-alternative-to-state-machines/">on implementing state machines via Python coroutines</a> I wanted to...</p>

<ul>
<li>see his example run under Python3</li>
<li>and also add the appropriate type annotations for the generators</li>
</ul>

<p>I succeeded in doing the first part (<em>but without using <code>async def</code>s or <code>yield from</code>s, I basically just ported the code - so any improvements there are most welcome</em>). </p>

<p>But I need some help with the type annotations of the coroutines:</p>

<pre><code>#!/usr/bin/env python3

from typing import Callable, Generator

def unwrap_protocol(header: int=0x61,
                    footer: int=0x62,
                    dle: int=0xAB,
                    after_dle_func: Callable[[int], int]=lambda x: x,
                    target: Generator=None) -&gt; Generator:
    """ Simplified protocol unwrapping co-routine."""
    #
    # Outer loop looking for a frame header
    #
    while True:
        byte = (yield)
        frame = []  # type: List[int]

        if byte == header:
            #
            # Capture the full frame
            #
            while True:
                byte = (yield)
                if byte == footer:
                    target.send(frame)
                    break
                elif byte == dle:
                    byte = (yield)
                    frame.append(after_dle_func(byte))
                else:
                    frame.append(byte)


def frame_receiver() -&gt; Generator:
    """ A simple co-routine "sink" for receiving full frames."""
    while True:
        frame = (yield)
        print('Got frame:', ''.join('%02x' % x for x in frame))

bytestream = bytes(
    bytearray((0x70, 0x24,
               0x61, 0x99, 0xAF, 0xD1, 0x62,
               0x56, 0x62,
               0x61, 0xAB, 0xAB, 0x14, 0x62,
               0x7)))

frame_consumer = frame_receiver()
next(frame_consumer)  # Get to the yield

unwrapper = unwrap_protocol(target=frame_consumer)
next(unwrapper)  # Get to the yield

for byte in bytestream:
    unwrapper.send(byte)
</code></pre>

<p>This runs properly...</p>

<pre><code>$ ./decoder.py 
Got frame: 99afd1
Got frame: ab14
</code></pre>

<p>...and also typechecks:</p>

<pre><code>$ mypy --disallow-untyped-defs decoder.py 
$
</code></pre>

<p>But I am pretty sure I can do better than just use the <code>Generator</code> base class in the type specs (just as I did for the <code>Callable</code>). I know it takes 3 type parameters (<code>Generator[A,B,C]</code>), but I am not sure how exactly they'd be specified here.</p>

<p>Any help most welcome.</p>