将变量类型提示为 Iterable 与 Sequence 之间有什么区别?

问题描述 投票:0回答:3

我不明白提示

Iterable
Sequence
时的区别。

这两者之间的主要区别是什么以及何时使用哪个?

我认为

set
Iterable
但不是
Sequence
,是否有任何内置数据类型是
Sequence
但不是
Iterable

def foo(baz: Sequence[float]):
  ...

# What is the difference?
def bar(baz: Iterable[float]):
  ...
python-3.x type-hinting
3个回答
60
投票

Sequence
Iterable
抽象基类(也可以用作类型注释)大多数*遵循Python对sequenceiterable的定义。具体来说:

  • Iterable 是定义
    __iter__
    __getitem__
    的任何对象。
  • Sequence 是定义
    __getitem__
    __len__
    的任何对象。根据定义,任何序列都是可迭代的。
    Sequence
    类还定义了其他方法,例如调用两个必需方法的
    __contains__
    __reversed__

一些例子:

  • list
    tuple
    str
    是最常见的序列。
  • 一些内置的可迭代对象不是序列。例如,
    reversed
    返回无法添加下标的
    reversed
    对象(或列表的
    list_reverseiterator
    )。

*

Iterable
并不完全符合 Python 对可迭代的定义——它只检查对象是否定义了
__iter__
,并且不适用于只能通过
__getitem__
进行迭代的对象(有关详细信息,请参阅此表)。检查对象是否可迭代的黄金标准是使用内置的
iter


10
投票

在编写带有

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

0
投票
    粗略地说,
  • Iterable
    是可以迭代的任何内容(使用
    for
    循环进行循环)。
  • Sequence
    是一个具有一些附加属性的可迭代对象:您可以获取其大小
    len(sequence)
    ,您可以通过位置访问其元素
    sequence[n]
    等。

向函数添加类型提示时,应遵循两个基本规则:

  1. 输入参数的类型应尽可能通用。
  2. 输出类型应尽可能具体。

看看这些例子:

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
感到惊讶。

© www.soinside.com 2019 - 2024. All rights reserved.