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 数组序列。如果我开始注释,我将如何处理序列?
如果您知道什么是结构类型、名义类型和鸭子类型,请跳过。
我认为大部分的混淆来自于typing3.5 和 3.6 版本之间的临时模块这一事实。并且仍然会在 3.7 和 3.8 版本之间发生变化。这意味着 Python 如何通过类型注释来处理输入的问题有很多变化。
python既是鸭子类型又是名义类型也无济于事。也就是说,在访问对象的属性时,Python 是鸭子类型的。该对象只会在运行时检查它是否具有属性,并且仅在立即请求时才会检查。然而,Python 也有名义上的输入特性(例如isinstance()和issubclass())。名义类型是将一种类型声明为另一种类型的子类。这可以通过继承,或与所述register()的方法ABCMeta。
typing最初使用名义类型的概念引入了它的类型。从 3.8 开始,它试图允许更多的 Pythonic 结构类型。结构类型与鸭子类型相关,只是它是在“编译时”而不是运行时考虑的。例如,当 linter 试图检测可能的类型错误时——例如,如果您将 a 传递dict给只接受元组或列表等序列的函数。对于结构类型,如果一个类实现了 的所有方法B,A那么它应该被认为是 的子类型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。请注意,所有这些都是单独检查每个协议方法是否可以在给定的对象/类上找到。因此,如果您的协议有很多方法,它会变得非常昂贵。
根据您粘贴的文档:
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。