带有 yield 的 Python 函数的正确类型注释

新手上路,请多包涵

在阅读了 Eli Bendersky 关于通过 Python 协程实现状态机 的文章后,我想……

  • 查看他的示例在 Python3 下运行
  • 并为生成器添加适当的类型注释

我成功地完成了第一部分( _但没有使用 async def s 或 yield from s,我基本上只是移植了代码 - 所以任何改进都是最受欢迎的_)。

但是我需要一些关于协程类型注释的帮助:

 #!/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) -> 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() -> 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)

这运行正常…

 $ ./decoder.py
Got frame: 99afd1
Got frame: ab14

…还有类型检查:

 $ mypy --disallow-untyped-defs decoder.py
$

但我很确定我可以做得更好,而不仅仅是在类型规范中使用 Generator 基类(就像我对 Callable 所做的那样)。我知道它需要 3 个类型参数 ( Generator[A,B,C] ),但我不确定它们在这里是如何指定的。

欢迎任何帮助。

原文由 ttsiodras 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 799
2 个回答

我自己想出了答案。

我进行了搜索,但在 Python 3.5.2 的官方打字文档中 没有找到 Generator 的 3 个类型参数的文档 - 除了真正神秘的提及……

 class typing.Generator(Iterator[T_co], Generic[T_co, T_contra, V_co])

幸运 的是,最初的 PEP484 (开始了这一切)更有帮助:

_“生成器函数的返回类型可以通过typing.py模块提供的泛型Generator[yield_type, send_type, returntype]注解:

 def echo_round() -> Generator[int, float, str]:
    res = yield
    while res:
        res = yield round(res)
    return 'OK'

基于此,我能够注释我的生成器,并看到 mypy 确认我的分配:

 from typing import Callable, Generator

# A protocol decoder:
#
# - yields Nothing
# - expects ints to be `send` in his yield waits
# - and doesn't return anything.
ProtocolDecodingCoroutine = Generator[None, int, None]

# A frame consumer (passed as an argument to a protocol decoder):
#
# - yields Nothing
# - expects List[int] to be `send` in his waiting yields
# - and doesn't return anything.
FrameConsumerCoroutine = Generator[None, List[int], None]

def unwrap_protocol(header: int=0x61,
                    footer: int=0x62,
                    dle :int=0xAB,
                    after_dle_func: Callable[[int], int]=lambda x: x,
                    target: FrameConsumerCoroutine=None) -> ProtocolDecodingCoroutine:
    ...

def frame_receiver() -> FrameConsumerCoroutine:
    ...

我通过例如交换类型的顺序来测试我的分配 - 然后如预期的那样, mypy 抱怨并要求正确的(如上所示)。

完整代码 可从此处访问

我会把这个问题悬而未决几天,以防万一有人想插话——尤其是在使用 Python 3.5 的新协程样式方面( async def 等)——我会很感激你的提示它们将如何在这里使用。

原文由 ttsiodras 发布,翻译遵循 CC BY-SA 3.0 许可协议

如果您有一个使用 yield 的简单函数,那么您可以使用 Iterator 类型来注释其结果,而不是 Generator

 from typing import Iterator

def count_up() -> Iterator[int]:
    for x in range(10):
        yield x

原文由 David Foster 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题