如何为非生成器(非消耗型)可迭代对象添加类型提示?
你遇到的问题核心是要区分一次性可迭代对象(比如生成器)和可重复迭代对象(比如list、dict的KeyView这类能多次遍历的)。生成器属于Iterator子类,只能迭代一次;而你需要的是每次调用iter()都能返回新迭代器的对象,这样嵌套循环才能正常工作。
这里有两种解决方案,你可以根据自己的场景选择:
方案1:用Collection[str](适合大多数常见场景)
如果你的需求覆盖的都是像KeyView、list、tuple、set这类标准容器类的可迭代对象,直接用Collection[str]作为类型提示就足够了:
from collections.abc import Collection def func(thing: Collection[str]) -> None: for i in range(10): for x in thing: do_thing(x)
Collection协议要求对象实现__iter__、__len__和__contains__,KeyView完全符合这个要求,而生成器属于Iterator,并不属于Collection——PyCharm会正确识别这一点,禁止传入生成器,同时允许你需要的那些可迭代对象。
方案2:自定义Reiterable协议(支持自定义可重复迭代对象)
如果你需要支持一些自己写的、没有__len__或__contains__方法但仍能重复迭代的对象,可以自定义一个协议,再结合类型排除来拦截迭代器:
from typing import Iterable, Iterator, Protocol, TypeVar from typing_extensions import Not T = TypeVar("T") class Reiterable(Protocol[T]): """标记可重复迭代的Iterable对象:每次调用iter()都会返回新的迭代器""" def __iter__(self) -> Iterator[T]: ... # 用Not排除Iterator类型,确保传入的不是生成器这类一次性迭代器 def func(thing: Not[Iterator[T]] & Reiterable[T]) -> None: for i in range(10): for x in thing: do_thing(x)
这样PyCharm会静态检查出传入生成器的错误,同时允许KeyView、list和你的自定义可重复迭代对象。
要是你还想加一层运行时的安全防护,可以写个类型守卫函数:
from typing import TypeGuard def is_reiterable(obj: Iterable[T]) -> TypeGuard[Not[Iterator[T]] & Reiterable[T]]: return not isinstance(obj, Iterator) def func(thing: Iterable[T]) -> None: if not is_reiterable(thing): raise TypeError("需要传入可重复迭代的对象,不能是生成器或迭代器") for i in range(10): for x in thing: do_thing(x)
内容的提问来源于stack exchange,提问作者Mattwmaster58
相关产品推荐
相关产品推荐

