如何使用类型提示在生成器上注释装饰器?

6

我正在使用生成器作为协程,正如David Beazley在其三个精彩演讲中所描述的那样(位于http://www.dabeaz.com/coroutines/),但我无法确定如何输入装饰器consumer。这是我目前的代码:

from typing import Any, Callable, Generator, Iterable

ArbGenerator = Generator[Any, Any, Any]

def consumer(fn: ❓) -> ❓:
    @wraps(fn)
    def start(*args: Any) -> ArbGenerator:
        c = fn(*args)
        c.send(None)
        return c
return start

使用示例,有点删节:

@consumer
def identity(target: ArbGenerator) -> ArbGenerator:
    while True:
        item = yield
        target.send(item)

@consumer
def logeach(label: Any, target: ArbGenerator) -> ArbGenerator:
    while True:
        item = yield
        print(label, item)
        target.send(item)

pipeline = identity(logeach("EXAMPLE", some_coroutine_sink()))

加粗的 标记表示我不确定 - 我也不确定我定义的类型 ArbGenerator。问题是,如果没有 (装饰器) 函数 consumer 自身被编写,我不确定 mypy 是否分析带有该装饰器的生成器函数,这就是我对 ArbGenerator 不确定的原因。

我对最紧密的类型感兴趣,比 Any 更好,这样当我组合这些协程链时,如果链条没有正确设置,mypy 将会给我一个很好的警告。

(如果有影响,则为 Python 3.5.)


2
我倾向于将显式类型视为可选项,这似乎是Python社区中最慷慨的类型应用。在这种情况下,似乎任何类型都比仅使用鸭子类型更加痛苦,因此我会省略它。 - Adam Smith
2
@AdamSmith - 说实话,我认为(省略显式类型)是 Pythonic 哲学 - 所以当我在我的新工作中(我对 Python 相对陌生)发现类型提示甚至存在,并且我的同事们正在大量使用它时,我有些惊讶。 - davidbak
这对你来说是个好消息!我很感兴趣看到一个好的解决方案,由于强显式类型和协程都是我的弱点领域,所以我在紧张地关注着这个问题 :) - Adam Smith
@davidbak 为什么不使用 typing.Coroutine - Mazdak
@Kasrâmvd - 谢谢你。由于某种原因,我认为Coroutine是特定于异步的。 - davidbak
1个回答

2
作为更具体的方法,以下是您可以执行的几个操作:
  1. 使用Callable类型而不是问号。

  2. 对于targets,请使用typing.Coroutine并删除ArbGenerator

  3. 协程返回一个生成器,返回类型可以是Generator或其超类型之一。

应该使用可调用对象而不是问号的原因是,fn最初应该是可调用对象,这就是为什么您要将其包装在装饰器中的原因。调用对象后将创建Coroutine,返回类型也显然应该是可调用对象。


"Original Answer"翻译成"最初的回答"
from typing import Any, Callable,Generator, Coroutine
from functools import wraps


def consumer(fn: Callable) -> Callable:
    @wraps(fn)
    def start(*args: Any) -> Coroutine:
        c = fn(*args)  # type: Coroutine
        c.send(None)
        return c
    return start


@consumer
def identity(target: Coroutine) -> Generator:
    while True:
        item = yield
        target.send(item)

@consumer
def logeach(label: Any, target: Coroutine) -> Generator:
    while True:
        item = yield
        print(label, item)
        target.send(item)

注意:正如文档中提到的那样,如果您想使用更精确的语法来注释生成器类型,可以使用以下语法:

最初的回答
Generator[YieldType, SendType, ReturnType]

阅读更多:https://docs.python.org/3/library/typing.html#typing.Generator

最初的回答

1
由于某些原因,我认为Coroutine只适用于异步操作。现在,我一直在运行mypy --strict,但这个解决方案几乎可以与mypy --strict --allow-any-generics一起使用,以具有非特定的CallableCoroutineGenerator:我编辑了它,以在包装的start中提示c,需要避免在return c处出现错误,该错误是Any不是Coroutine - davidbak
我猜想我想知道为什么协程的返回类型不是Coroutine,因为它们显然是(yield在赋值语句的右侧)? - davidbak
@davidbak 因为它们不返回“Coroutine”。实际上,它们返回None(Python中表示任何对象的缺失的抽象)。但是,如果您想使生成器的类型更加精确,可以使用Generator[YieldType, SendType, ReturnType]语法。在此处阅读更多信息:https://docs.python.org/3/library/typing.html#typing.Generator - Mazdak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接