web-dev-qa-db-ja.com

Python関数の適切な型注釈とyield

Eli Benderskyの記事を読んだ後 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])が必要であることはわかっていますが、それらがここでどのように指定されるのか正確にはわかりません。

どんな助けでも大歓迎です。

25
ttsiodras

私は自分で答えを見つけました。

私は検索しましたが、Generatorの3つの型パラメーターに関するドキュメントは見つかりませんでした Python 3.5.2)の公式タイピングドキュメント -本当に不可解な言及を超えて...

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

幸い、 元のPEP484 (これがすべて始まった)の方がはるかに役に立ちました。

"ジェネレーター関数の戻り値の型は、typeing.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は文句を言い、適切なものを要求しました(上記参照)。

完全なコード ここからアクセス可能

特にPython 3.5(async def、)の新しいコルーチンスタイルの使用に関して)誰かが参加したい場合に備えて、質問を数日間開いたままにします。 -ここでそれらがどのように使用されるかについてのヒントを教えていただければ幸いです。

31
ttsiodras

yieldを使用する単純な関数がある場合は、IteratorではなくGeneratorタイプを使用して、その結果に注釈を付けることができます。

from typing import Iterator

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