【问题标题】:What is Python's sequence protocol?Python的序列协议是什么?
【发布时间】:2017-04-23 00:15:59
【问题描述】:

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

例如,C API 函数PySequence_Check 检查(根据文档)是否某个对象实现了“序列协议”。 source code 表示这是一个不是 dict 的类,但实现了 __getitem__ 方法,该方法大致iter 上的文档中的说明相同:

[...]必须支持序列协议(__getitem__() 方法,整数参数从 0 开始)。[...]

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

还有collections.abc.Sequence类型,基本上就是说实例要实现__reversed____contains____iter____len__

但是根据该定义,实现“序列协议”的类不一定是序列,例如"data model" 和抽象类保证序列具有长度。但是一个只实现__getitem__(通过PySequence_Check)的类在使用len(an_instance_of_that_class)时会抛出异常。

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

【问题讨论】:

  • collections.abc.Sequence 需要 __getitem____len__。其他一切都有 mixin 方法。关于迭代,如果只定义了__getitem__ 而没有定义__iter__,那么内置的iter 会实例化一个从索引0 开始的简单迭代器。要使reversed 工作__len__ 也必须定义,所以它可以从最后一个索引开始。
  • @eryksun 但是一个类不需要__len__ 来实现序列协议(就PySequence_Check 而言)。实现__len____getitem__ 但不从collections.abc.Sequence 继承的类不会传递isinstance(an_instance, Sequence)。这就是触发我的问题的原因。 :)
  • 这里PySequence_Check有很好的详细解释:grokbase.com/t/python/python-list/054erpfcep/…

标签: python sequence cpython python-internals


【解决方案1】:

这不是真的一致。

这里是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 要求其参数不是 dict,并且它提供 sq_item

用 Python 编写的带有__getitem__ 的类型将提供sq_item,无论它们在概念上是序列还是映射,因此不从dict 继承的用Python 编写的映射将传递PySequence_Check


另一方面,collections.abc.Sequence 只检查一个对象是否具体继承自collections.abc.Sequence,或者它的类(或超类)是否明确地registered 和collections.abc.Sequence。如果你只是自己实现一个序列而不做任何这些事情,它不会通过isinstance(your_sequence, Sequence)。此外,大多数使用collections.abc.Sequence 注册的类并不支持collections.abc.Sequence 的所有方法。总体而言,collections.abc.Sequence 的可靠性远不如人们通常预期的那么可靠。


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

【讨论】:

  • 我认为PySequence_Check 不包括dicts,因为子类可以实现__getitem__,至于自定义类,它们在实现True 时会返回Truegist.github.com/MSeifert04/e39d91f9d262618a32f7db14aaab15f4。感谢您的回答(特别是指出 collections.abc.Sequence 没有 __subclasshook__ 对我来说是新的),如果其他人想提供答案,我将在另一天不接受它。
  • @MSeifert:是的,我错了关于用户定义的类和sq_item。我可以发誓没有处理通过包装__getitem__ 来提供sq_item,但显然有,而且它不是新的。
  • 请参阅issue 23864,了解issubclass 的限制,其中的 ABC 不是“one-trick ponies”。它似乎总是不必要地局限在我身上。
  • @eryksun 我已经问过 Guido 是否可以在您的问题中添加缺少的 __subclasshook__,因为它仍然处于打开状态。顺便问一下,你知道为什么Collections Abstract Base Classes 的表缺少一些mixin 方法的抽象(例如Reversible 类缺少mixin 方法__iter__Coroutine 类缺少抽象方法@ 987654364@)?
  • @Maggyero:这些都是继承的抽象方法。 ReversibleIterable 继承 __iter__CoroutineAwaitable 继承 __await__。 (另外,__iter__ 不是 mixin 方法,因为根据 __reversed__ 实现 __iter__ 效率低下且奇怪。)
【解决方案2】:

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

  • 按索引检索元素

    item = seq[index]

  • 按值查找项目

    index = seq.index(item)

  • 计数项目

    num = seq.count(item)

  • 产生一个相反的序列

    r = reversed(seq)

【讨论】:

  • 您能否链接此信息的来源或解释您是如何知道的?
  • 我同意提供此信息的来源是谨慎的做法。
猜你喜欢
  • 2011-04-28
  • 2017-08-06
  • 2016-05-30
  • 2019-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-03
  • 1970-01-01
相关资源
最近更新 更多