Python 的序列协议是什么?

What is Python's sequence protocol?

Python 用魔术方法做了很多,其中大部分是某些协议的一部分。我熟悉“迭代器协议”和“数字协议”,但最近偶然发现了术语 "sequence protocol"。但即使经过一些研究,我也不确定“序列协议”是什么。

例如 C API 函数 PySequence_Check checks (according to the documentation) if some object implements the "sequence protocol". The source code indicates that this is a class that's not a dict but implements a __getitem__ method which is roughly identical to what the documentation on iter 也声明:

[...]must support the sequence protocol (the __getitem__() method with integer arguments starting at 0).[...]

但是以 0 开头的要求在 PySequence_Check 中并未“实现”。

然后还有collections.abc.Sequence类型,基本上说实例必须实现__reversed____contains____iter____len__

但根据该定义,实现“序列协议”的 class 不一定是序列,例如 "data model" 和抽象 class 保证序列具有长度。但是 class 只是实现 __getitem__(传递 PySequence_Check)在使用 len(an_instance_of_that_class).

时抛出异常

有人可以为我澄清序列和序列协议之间的区别(如果除了阅读源代码之外还有协议的定义)以及何时使用哪个定义?

不太一致。

这里是PySequence_Check

int
PySequence_Check(PyObject *s)
{
    if (PyDict_Check(s))
        return 0;
    return s != NULL && s->ob_type->tp_as_sequence &&
        s->ob_type->tp_as_sequence->sq_item != NULL;
}

PySequence_Check 检查对象是否提供 C 序列协议,通过 PyTypeObject 中表示对象类型的 tp_as_sequence 成员实现。这个 tp_as_sequence 成员是一个指向结构的指针,该结构包含一系列用于序列行为的函数,例如用于通过数字索引检索项目的 sq_item 和用于项目分配的 sq_ass_item

具体来说,PySequence_Check 要求其参数不是字典,并且它提供 sq_item.

在 Python 中写有 __getitem__ 的类型将提供 sq_item,无论它们在概念上是序列还是映射,所以在 Python 中写的映射不'继承自 dict 将传递 PySequence_Check


另一方面,collections.abc.Sequence 只检查一个对象是否具体地继承自 collections.abc.Sequence 或者它的 class(或一个 superclass)是否明确地 register编辑为 collections.abc.Sequence。如果你只是自己实现一个序列而不做这些事情,它不会通过 isinstance(your_sequence, Sequence)。此外,大多数在 collections.abc.Sequence 注册的 classes 并不支持 collections.abc.Sequence 的所有方法。总的来说,collections.abc.Sequence 远不如人们通常预期的那样可靠。


至于在实践中什么算作序列,它通常是任何支持 __len____getitem__ 且整数索引从 0 开始并且不是映射的东西。如果一个函数的文档说它需要任何序列,那几乎总是它所需要的。不幸的是,“不是一个映射”很难测试,原因类似于“是一个序列”很难确定。

要使类型符合序列协议,必须满足这 4 个条件:

  • 按索引检索元素

    item = seq[index]

  • 按值查找项目

    index = seq.index(item)

  • 计数项目

    num = seq.count(item)

  • 产生一个反转序列

    r = reversed(seq)