究竟什么是序列?

Pau*_*zer 6 python numpy sequence

Python文档有点模棱两可

序列

一个通过__getitem__()特殊方法支持使用整数索引的有效元素访问的可迭代对象,并定义了一个__len__() 返回序列长度的方法。一些内置的序列类型是列表、字符串、元组和字节。请注意, dict 也支持 __getitem__()and __len__(),但被视为映射而不是序列,因为查找使用任意不可变键而不是整数。

collections.abc.Sequence 抽象基类定义了一个更丰富的接口,它超越了__getitem__()and __len__(),添加了 count()、index() __contains__()、 和__reversed__()。可以使用 register() 显式注册实现此扩展接口的类型。

特别是,使用某些推荐abc.collections.Sequence的黄金标准意味着,例如,numpy 数组不是序列:

isinstance(np.arange(6),collections.abc.Sequence)
# False
Run Code Online (Sandbox Code Playgroud)

还有一种叫做序列 协议的东西,但它似乎只在 C-API 中公开。那里的标准是

int PySequence_Check(PyObject *o)

如果对象提供序列协议,则返回 1,否则返回 0。请注意,__getitem__()除非它们是 dict 子类,否则它为带有方法的Python 类返回 1, 因为在一般情况下,无法确定它支持的键类型。此功能总是成功。

最后,我不会太密切关注这个新的 (-ish) 类型注释业务,但我想这也将受益于序列是什么的清晰概念。

所以我的问题既有哲学的一面,也有实际的一面:序列到底是什么?以及如何测试某事物是否为序列?理想情况下,以某种方式制作 numpy 数组序列。如果我开始注释,我将如何处理序列?

Dun*_*nes 5

Python 打字简介

如果您知道什么是结构类型、名义类型和鸭子类型,请跳过。

我认为大部分的混淆来自于typing3.5 和 3.6 版本之间的临时模块这一事实。并且仍然会在 3.7 和 3.8 版本之间发生变化。这意味着 Python 如何通过类型注释来处理输入的问题有很多变化。

python既是鸭子类型又是名义类型也无济于事。也就是说,在访问对象的属性时,Python 是鸭子类型的。该对象只会在运行时检查它是否具有属性,并且仅在立即请求时才会检查。然而,Python 也有名义上的输入特性(例如isinstance()issubclass())。名义类型是将一种类型声明为另一种类型的子类。这可以通过继承,或与所述register()的方法ABCMeta

typing最初使用名义类型的概念引入了它的类型。从 3.8 开始,它试图允许更多的 Pythonic 结构类型。结构类型与鸭子类型相关,只是它是在“编译时”而不是运行时考虑的。例如,当 linter 试图检测可能的类型错误时——例如,如果您将 a 传递dict给只接受元组或列表等序列的函数。对于结构类型,如果一个类实现了 的所有方法BA那么它应该被认为是 的子类型A,不管它是否被声明为 的子类型A(如在名义类型中)。

回答

序列(小 s)是鸭子类型。序列是提供对其成员的随机访问的任何有序对象集合。具体来说,如果它定义__len____getitem__使用 0 到 n-1 之间的整数索引,那么它就是一个序列。A Sequence (big s) 是一种名义类型。也就是说,要成为 Sequence,必须通过继承 Sequence 或注册为子类来声明类。

numpy 数组一个序列,但它不是Sequence,因为它没有注册为 Sequence 的子类。也不应该是,因为它没有实现 Sequence 承诺的完整接口(例如count()index()缺少的东西)。

听起来您想要的是序列的结构化类型(小 s)。从 3.8 开始,这可以通过使用协议来实现。协议定义了一组方法,类必须实现这些方法才能被视为协议的子类(结构类型)。

from typing import Protocol
import numpy as np

class MySequence(Protocol):
    def __getitem__(self, index):
        raise NotImplementedError
    def __len__(self):
        raise NotImplementedError
    def __contains__(self, item):
        raise NotImplementedError
    def __iter__(self):
        raise NotImplementedError

def f(s: MySequence):
    for i in range(len(s)):
        print(s[i], end=' ')
    print('end')

f([1, 2, 3, 4]) # should be fine
arr: np.ndarray = np.arange(5)
f(arr) # also fine
f({}) # might be considered fine! Depends on your type checker
Run Code Online (Sandbox Code Playgroud)

协议相当新,因此并非所有 IDE/类型检查器都可能支持它们。我使用的 IDE PyCharm 可以。它不喜欢f({}),但很高兴将 numpy 数组视为序列(大 S)(可能并不理想)。您可以通过使用 的runtime_checkable装饰器启用协议的运行时检查typing。请注意,所有这些都是单独检查每个协议方法是否可以在给定的对象/类上找到。因此,如果您的协议有很多方法,它会变得非常昂贵。


Sam*_*ord 0

根据您粘贴的文档:

collections.abc.Sequence 抽象基类定义了一个更丰富的接口,它不仅仅是__getitem__()and __len__(),还添加了count(), index(), __contains__(), 和__reversed__()。实现此扩展接口的类型可以使用 register() 显式注册。

numpy.ndarray没有实现该Sequence协议,因为它没有实现count()index()

>>> arr = numpy.arange(6)
>>> isinstance(arr, Sequence)
False
>>> arr.count(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'numpy.ndarray' object has no attribute 'count'
>>> arr.index(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'numpy.ndarray' object has no attribute 'index'
Run Code Online (Sandbox Code Playgroud)

对比a range

>>> r = range(6)
>>> isinstance(r, Sequence)
True
>>> r.count(3)
1
>>> r.index(3)
3
Run Code Online (Sandbox Code Playgroud)

如果你想声明这arr是一个Sequence,你可以使用register()类方法:

>>> Sequence.register(numpy.ndarray)
<class 'numpy.ndarray'>
>>> isinstance(arr, Sequence)
True
Run Code Online (Sandbox Code Playgroud)

但这是一个谎言,因为它实际上并没有实现协议(该register()函数实际上并没有检查这一点,它只是信任你):

>>> arr.count(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'numpy.ndarray' object has no attribute 'count'
Run Code Online (Sandbox Code Playgroud)

numpy.ndarray因此,如果将 a 传递给需要 a 的函数,这样做可能会导致错误Sequence