快速提醒:你的函数是一个“返回生成器的常规函数”,而不是“生成器函数”。要理解这个区别,请阅读
这个答案。
对于你的
foo
,我建议使用
-> Iterator[int]
。
解释
这归结于你想要什么样的接口。
首先,熟悉一下Python文档中定义了最重要的Python类型层次结构的
这个页面。
你可以看到这些表达式返回
True
:
import typing as t
issubclass(t.Iterator, t.Iterable)
issubclass(t.Generator, t.Iterator)
你还应该注意到同一页上,
Generator
有一些
Iterator
没有的方法。这些方法是
send
、
throw
和
close
(
文档),它们允许你在生成器中做更多的事情,而不仅仅是简单的单次迭代。查看这个问题以了解使用生成器的可能性的示例:
Python生成器上的"send"函数的目的是什么?
回到选择接口的问题。如果你希望其他人像使用生成器一样使用你的生成器函数的结果,也就是说,
def gen(limit: int): -> Generator[int, None, None]
for x in range(limit):
yield x
g = gen(3)
next(g)
g.send(10)
然后你应该指定
-> 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`。
注意:这种思路主要适用于
返回值的注释类型。对于参数的情况,你应该根据你在函数内部想要使用的接口(即方法/函数)来指定类型。
Iterable
/Iterator
的正式定义在collections.abc
模块中。如果您查看源代码,Iterable
的定义非常广泛:它只是“实现__iter__
方法的任何内容”。Iterator
的定义是“实现__next__
方法的任何内容”。如果一个类实现了__next__
,则可以推断出__iter__
(不需要显式定义)。因此,所有迭代器都是可迭代的,但并非所有可迭代的对象都是迭代器。https://github.com/python/cpython/blob/d3eaf0cc5b311ad023fd13e367f817d528403306/Lib/_collections_abc.py#L253 - Alex Waygood