Python的PEP 484类型注释用于生成器表达式

26
什么是返回生成器表达式的函数的正确类型注释?例如:
def foo():
    return (x*x for x in range(10))

我无法确定这是-> Iterator[int]-> Iterable[int]-> Generator[int, None, None]还是其他什么。

如果有一种——最好只有一种——明显的方法来做到这一点,那么这里的明显方法是什么?

2个回答

25
您所提到的三种形式都被列为文档中有效的替代方案,生成器表达式只创建一个仅产出的生成器。
引用1:
生成器可以通过通用类型Generator[YieldType, SendType, ReturnType]进行注释。
引用2:
如果您的生成器只会产出值,则将SendTypeReturnType设置为None
引用3:
或者,将您的生成器注释为返回类型为Iterable[YieldType]Iterator[YieldType]之一。

我理解生成器(在您有SendType和ReturnType的情况下)之间的区别,但我不理解Iterable和Iterator之间的区别。或者说,我不明白Iterable为什么是正确的。Iterable难道不意味着您可以从中派生多个迭代器(例如List、Set、Tuple)吗?而生成器一旦被消耗,就结束了。那么Iterable不正确吗? - Ehsan Kia
1
@EhsanKia,Iterable/Iterator的正式定义在collections.abc模块中。如果您查看源代码,Iterable的定义非常广泛:它只是“实现__iter__方法的任何内容”。Iterator的定义是“实现__next__方法的任何内容”。如果一个类实现了__next__,则可以推断出__iter__(不需要显式定义)。因此,所有迭代器都是可迭代的,但并非所有可迭代的对象都是迭代器。https://github.com/python/cpython/blob/d3eaf0cc5b311ad023fd13e367f817d528403306/Lib/_collections_abc.py#L253 - Alex Waygood

10
快速提醒:你的函数是一个“返回生成器的常规函数”,而不是“生成器函数”。要理解这个区别,请阅读这个答案
对于你的foo,我建议使用-> Iterator[int]
解释
这归结于你想要什么样的接口。
首先,熟悉一下Python文档中定义了最重要的Python类型层次结构的这个页面
你可以看到这些表达式返回True
import typing as t
issubclass(t.Iterator, t.Iterable)
issubclass(t.Generator, t.Iterator)

你还应该注意到同一页上,Generator有一些Iterator没有的方法。这些方法是sendthrowclose文档),它们允许你在生成器中做更多的事情,而不仅仅是简单的单次迭代。查看这个问题以了解使用生成器的可能性的示例:Python生成器上的"send"函数的目的是什么?

回到选择接口的问题。如果你希望其他人像使用生成器一样使用你的生成器函数的结果,也就是说,

def gen(limit: int): -> Generator[int, None, None]
    for x in range(limit):
        yield x

g = gen(3)
next(g)  # => 0
g.send(10)  # => 1

然后你应该指定-> Generator[int, None, None]
但请注意上面是胡说八道。实际上,你确实可以调用send,但它不会改变执行,因为gen对发送的值不做任何处理(没有类似x = yield的语句)。 知道了这一点,你可以限制使用gen的人的知识,并将其定义为-> Iterator[int]。通过这种方式,你可以与用户达成协议,即“我的函数返回整数的迭代器,你应该按照这样的方式使用它”。如果以后你改变了实现,比如...
def gen(limit: int): -> Iterator[int]
    return iter(list(range(limit)))

那些使用返回对象(因为他们窥视了实现)的人会导致他们的代码出错。然而,你不必为此烦恼,因为他们使用的方式与你的合同规定的方式不同。因此,这种破坏不是你的责任。
简单来说,如果你最终得到的是`Generator[Something, None, None]`(两个`None`),那么请考虑使用`Iterable[Something]`或者`Iterator[Something]`。
对于`Iterator`和`Iterable`也是一样的。如果你希望用户只能使用`iter`函数来操作你的对象(从而在迭代上下文中使用,例如`[x for x in g]`),那么请使用`Iterable`。如果你希望他们既可以使用`next`又可以使用`iter`来操作对象,请使用`Iterator`。
注意:这种思路主要适用于返回值的注释类型。对于参数的情况,你应该根据你在函数内部想要使用的接口(即方法/函数)来指定类型。

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