【问题标题】:Proper type annotation of Python functions with yield使用 yield 的 Python 函数的正确类型注释
【发布时间】:2016-11-20 01:17:23
【问题描述】:

读完 Eli Bendersky 的文章 on implementing state machines via Python coroutines 我想...

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

我成功完成了第一部分(但没有使用async defs 或yield froms,我基本上只是移植了代码 - 所以欢迎任何改进)。

但我需要一些协程类型注释方面的帮助:

#!/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]),但我不确定它们在此处具体如何指定。

欢迎任何帮助。

【问题讨论】:

    标签: python generator coroutine static-typing mypy


    【解决方案1】:

    我自己想出了答案。

    我进行了搜索,但在 official typing documentation for Python 3.5.2 中没有找到关于 Generator 的 3 个类型参数的文档 - 除了真正神秘地提及...

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

    幸运的是,the original PEP484(开始这一切)更有帮助:

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

    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 抱怨并要求正确的类型(如上所示)。

    完整代码is accessible from here

    我将把这个问题留几天,以防有人想插话——尤其是在使用 Python 3.5 的新协程样式(async def 等)方面——我将不胜感激它们将如何在这里使用。

    【讨论】:

    • 关于async def 和朋友们——他们目前不受mypy 支持,但正在积极开发中/应该在不久的将来准备好!有关详细信息,请参阅 github.com/python/mypy/pull/1808github.com/python/mypy/issues/1886
    • 仅供参考,mypy 0.4.4(对 async/await 具有实验性支持)was just released。您可以在mypy docs 中找到有关键入协程和 async/await 的更多信息。目前,我认为打字模块本身的文档没有提到与 async/await 相关的内容,但这可能会在接下来的几天内修复。
    • 谢谢,迈克尔 - 会检查一下。
    • Python 3.8.3 文档更具描述性:“生成器可以由通用类型 Generator[YieldType, SendType, ReturnType] 进行注释。” docs.python.org/3/library/typing.html#typing.Generator
    【解决方案2】:

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

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

    【讨论】:

    • 这是applicable,如果生成器只产生值,既不接收也不返回值。
    • Starting with Python 3.7, from typing import Iterator 不再需要,collections.abc.Iterator 替换 Iterator
    • 一个必须import collections.abc
    【解决方案3】:

    在撰写本文时,Python documentation 也明确提到了如何处理异步案例(非异步示例已在接受的答案中提及)。

    从那里引用:

    async def echo_round() -> AsyncGenerator[int, float]:
        sent = yield 0
        while sent >= 0.0:
            rounded = await round(sent)
            sent = yield rounded
    

    (第一个参数是yield-type,第二个是send-type)或者对于简单的情况(其中send-type是None)

    async def infinite_stream(start: int) -> AsyncIterator[int]:
        while True:
            yield start
            start = await increment(start)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-04
      • 2021-05-23
      • 1970-01-01
      • 2020-01-28
      • 2021-08-16
      • 1970-01-01
      相关资源
      最近更新 更多