我不明白提示
Iterable
和Sequence
时的区别。
这两者之间的主要区别是什么以及何时使用哪个?
我认为
set
是 Iterable
但不是 Sequence
,是否有任何内置数据类型是 Sequence
但不是 Iterable
?
def foo(baz: Sequence[float]):
...
# What is the difference?
def bar(baz: Iterable[float]):
...
Sequence
和Iterable
抽象基类(也可以用作类型注释)大多数*遵循Python对sequence和iterable的定义。具体来说:
__iter__
或 __getitem__
的任何对象。__getitem__
和 __len__
的任何对象。根据定义,任何序列都是可迭代的。 Sequence
类还定义了其他方法,例如调用两个必需方法的__contains__
、__reversed__
。一些例子:
list
、tuple
、str
是最常见的序列。reversed
返回无法添加下标的 reversed
对象(或列表的 list_reverseiterator
)。*
Iterable
并不完全符合 Python 对可迭代的定义——它只检查对象是否定义了 __iter__
,并且不适用于只能通过 __getitem__
进行迭代的对象(有关详细信息,请参阅此表)。检查对象是否可迭代的黄金标准是使用内置的 iter
。
在编写带有
items
参数的函数/方法时,我通常更喜欢 Iterable
而不是 Sequence
。
下面是原因,我希望它能帮助理解其中的区别。
说
my_func_1
是:
from typing import Iterable
def my_func_1(items: Iterable[int]) -> None:
for item in items:
...
if condition:
break
return
Iterable
为呼叫者提供最大的可能性。正确的调用包括:
my_func_1((1, 2, 3)) # tuple is Sequence, Collection, Iterator
my_func_1([1, 2, 3]) # list is MutableSequence, Sequence, Collection, Iterator
my_func_1({1, 2, 3}) # set is Collection, Iterator
my_func_1(my_dict) # dict is Mapping, Collection, Iterator
my_func_1(my_dict.keys()) # dict.keys() is MappingKeys, Set, Collection, Iterator
my_func_1(range(10)) # range is Sequence, Collection, Iterator
my_func_1(x**2 for x in range(100)) # "strict' Iterator, i.e. neither a Collection nor a Sequence
...
...因为都是
Iterable
。
给函数调用者的隐式消息是:“按原样”传输数据,只是不转换它。
如果调用者没有
Sequence
(例如 tuple
、list
)或非 Sequence
Collection
(例如 set
)数据,并且因为迭代在 之前中断, StopIteration
,如果他提供‘严格’的话,表现也会更好Iterator
。
但是,如果函数算法(例如
my_func_2
)需要多次迭代,那么如果调用者提供“严格”Iterable
,Iterator
将会失败,因为第一次迭代耗尽了它。因此使用 Collection
:
from typing import Collection
def my_func_2(items: Collection[int]) -> None:
for item in items:
...
for item in items:
...
return
如果函数算法(
my_func_3)
必须通过索引访问特定项,那么如果调用者提供集合、Iterable
或“严格”Collection
,Mapping
和Iterator
都将失败。
因此使用 Sequence
:
from typing import Sequence
def my_func_3(items: Sequence[int]) -> None:
return items[5]
结论:策略是:“使用函数可以处理的最通用的类型”。不要忘记,所有这些只是关于键入,以帮助静态类型检查器报告不正确的调用(例如,当需要
set
时使用 Sequence
)。然后调用者有责任在必要时转换数据,例如:
my_func_3(tuple(x**2 for x in range(100)))
实际上,所有这些实际上都与缩放项目长度时的性能有关。 如果可能的话,总是更喜欢
Iterator
。表演应作为日常任务来处理,而不是作为消防员工作组。
在这个方向上,您可能会遇到这样的情况:一个函数仅处理空用例并委托其他用例,并且您不想将项目转换为
Collection
或 Sequence
。然后做这样的事情:
from more_itertools import spy
def my_func_4(items: Iterable[int]) -> None:
(first, items) = spy(items)
if not first: # i.e. items is empty
...
else:
my_func_1(items) # Here 'items' is always a 'strict' Iterator
return
Iterable
是可以迭代的任何内容(使用 for
循环进行循环)。Sequence
是一个具有一些附加属性的可迭代对象:您可以获取其大小 len(sequence)
,您可以通过位置访问其元素 sequence[n]
等。向函数添加类型提示时,应遵循两个基本规则:
看看这些例子:
from typing import Iterable, List
def foo(numbers: Iterable[int]) -> List[int]:
digits = list()
for number in numbers:
if 0 <= number <= 9:
digits.append(number)
return digits
功能
foo
搜索单位数字。如果您对参数 Sequence[int]
使用更严格的类型提示 numbers
,它将向函数的用户指示不允许使用 set
等非序列迭代。但它们工作得很好!您希望您的函数支持尽可能广泛的输入(以使大多数用户满意),因此添加此类限制是没有意义的。
另一方面,输出类型
List[int]
非常具体,因此该函数的用户可以获得有关他们获得的输出的详细信息。
from typing import Sequence
def bar(numbers: Sequence[int]) -> bool:
sorted = True
for i in range(len(numbers) - 1):
if numbers[i] > numbers[i+1]:
sorted = False
return sorted
函数
bar
检查数字是否按升序排列。这里需要更严格的类型提示Sequence[int]
,因为代码使用了并非所有可迭代对象都支持的序列的某些功能。如果您使用类型提示 Iterable[int]
,则用户可能会想输入 set
作为参数,并对异常 TypeError: 'set' object does not support indexing
感到惊讶。