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

tts*_*ras 21 python static-typing generator coroutine mypy

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

  • 看他在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)
Run Code Online (Sandbox Code Playgroud)

这运行正常......

$ ./decoder.py 
Got frame: 99afd1
Got frame: ab14
Run Code Online (Sandbox Code Playgroud)

......还有typechecks:

$ mypy --disallow-untyped-defs decoder.py 
$
Run Code Online (Sandbox Code Playgroud)

但我很确定我能做的不仅仅是Generator在类型规范中使用基类(正如我所做的那样Callable).我知道它需要3个类型的参数(Generator[A,B,C]),但我不确定它们在这里是如何指定的.

欢迎任何帮助.

Dav*_*ter 33

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

from typing import Iterator

def count_up() -> Iterator[int]:
    for x in range(10):
        yield x
Run Code Online (Sandbox Code Playgroud)

  • 如果生成器仅生成值而不接收也不返回值,则这是[适用](https://docs.python.org/3/library/typing.html#typing.Generator)。 (3认同)

tts*_*ras 29

我自己想出了答案.

我搜索,但没有发现文档的3个类型参数Generator的Python 3.5.2正式打字文件 -超越的真正神秘的提...

class typing.Generator(Iterator[T_co], Generic[T_co, T_contra, V_co])
Run Code Online (Sandbox Code Playgroud)

幸运的是,最初的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'
Run Code Online (Sandbox Code Playgroud)

基于此,我能够注释我的生成器,并看到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:
    ...
Run Code Online (Sandbox Code Playgroud)

我测试了我的作业,例如交换类型的顺序 - 然后按预期,mypy抱怨并要求正确的(如上所示).

完整的代码可以从这里访问.

我将问题保持开放几天,以防任何人想要加入 - 尤其是在使用Python 3.5(async def等等)的新协程样式方面 - 我会很感激他们如何使用它们这里.

  • 关于`async def`和朋友 - 他们目前不受mypy支持,但正在积极开发/应该在不久的将来做好准备!有关详细信息,请参阅https://github.com/python/mypy/pull/1808和https://github.com/python/mypy/issues/1886. (3认同)
  • Python 3.8.3 文档更具描述性:“生成器可以通过通用类型 Generator[YieldType, SendType, ReturnType] 进行注释。” https://docs.python.org/3/library/typing.html#typing.Generator (3认同)

小智 5

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

从那里引用:

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

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

async def infinite_stream(start: int) -> AsyncIterator[int]:
    while True:
        yield start
        start = await increment(start)
Run Code Online (Sandbox Code Playgroud)