为什么在Python 2中bytearray不是一个序列?

12

我发现Python 2和3之间的行为存在奇怪的差异。

在Python 3中,事情似乎正常工作:

Python 3.5.0rc2 (v3.5.0rc2:cc15d736d860, Aug 25 2015, 04:45:41) [MSC v.1900 32 b
it (Intel)] on win32
>>> from collections import Sequence
>>> isinstance(bytearray(b"56"), Sequence)
True

但在 Python 2 中不行:

Python 2.7.10 (default, May 23 2015, 09:44:00) [MSC v.1500 64 bit (AMD64)] on wi
n32
>>> from collections import Sequence
>>> isinstance(bytearray("56"), Sequence)
False

这些结果在Python 2.x和3.x的小版本之间似乎是一致的。这是一个已知的 bug 吗?它真的是一个 bug 吗?这种差异背后有任何逻辑吗?

我实际上更担心 C API 函数 PySequence_Check 能否正确地将 PyByteArray_Type 类型的对象识别为公开序列协议,通过查看源代码,它似乎应该如此,但对整个事情的任何洞见都非常欢迎。


你可以在 PEP 3137 -- 不可变字节和可变缓冲区PEP 3119 -- 引入抽象基类 中找到你的答案。 - Mazdak
4
根据PEP 3119,表达式issubclass(bytearray, MutableSequence)的值为真...但在Python2中并非如此。据我所知,PySequence_Check将返回正确的结果,因为它使用元类来确定一个类是否是序列。它查看包含序列方法的struct字段是否设置。 - Bakuriu
2个回答

7
collections中的抽象类使用ABCMeta.register(subclass)subclass注册为此ABC的“虚拟子类”。在Python 3中,issubclass(bytearray, Sequence)返回True,因为bytearray被明确注册为ByteString(从Sequence派生)和MutableSequence的子类。请参见Lib/_collections_abc.py的相关部分。
class ByteString(Sequence):

    """This unifies bytes and bytearray.

    XXX Should add all their methods.
    """

    __slots__ = ()

ByteString.register(bytes)
ByteString.register(bytearray)
...
MutableSequence.register(bytearray)  # Multiply inheriting, see ByteString

Python 2不支持这个功能(来自Lib/_abcoll.py):

Sequence.register(tuple)
Sequence.register(basestring)
Sequence.register(buffer)
Sequence.register(xrange)
...
MutableSequence.register(list)

在Python 3.0中(具体来说是此提交)更改了这种行为:

添加ABC ByteString,它统一了bytesbytearray(但不包括memoryview)。没有“PEP 3118样式缓冲API对象”的ABC,因为在Python中无法识别它们(除非尝试在它们上使用memoryview())。

PEP 3119中还有更多信息:

这是一个向Python 3000添加抽象基类(ABC)支持的提案。它提议: [...] 特定的容器和迭代器ABC,将被添加到collections模块中。

该提案的许多思考不是关于ABC的具体机制,与接口或通用函数(GF)相对比,而是关于澄清哲学问题,例如“什么构成了集合”,“什么构成了映射”以及“什么构成了序列”。

[...]用于与ABC一起使用的元类,它允许我们将ABC添加为“虚拟基类”(与C++中的概念不同)到任何类中,包括另一个ABC。这允许标准库定义ABCsSequenceMutableSequence并将其注册为内置类型(例如basestringtuplelist)的虚拟基类,以便例如以下条件都成立:[...] issubclass(bytearray, MutableSequence)

仅供参考,memoryview仅在Python 3.4中被注册为Sequence的子类:

由于Sequence/Mapping的混淆,因此没有鸭式辨型,所以这是一个简单的缺少显式注册。

(有关详细信息,请参见issue18690)。


Python C API中的PySequence_Check不依赖于collections模块:

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;
}

它检查非零的tp_as_sequence字段(bytearray为例的示例),如果成功,则检查非零的sq_item字段(这基本上是getitem - bytearray为例的示例)。

4
当您查看Python3中的_collections_abc.py文件中collections抽象类的源代码时,您会发现ByteString类是Sequence类的子类,并且使用bytearray注册自己,而在Python2(文件_abcoll.py)中没有ByteString类,Sequence类也不使用bytearray注册自己。

通过“register”,我指的是抽象类Sequence(或其子类ByteString)调用abc.ABCMeta.register方法,如此方法描述所述:将子类注册为此ABC的“虚拟子类”。

我认为这导致了Py2和Py3之间的不同行为,但我认为这是一个错误(或者在Py3中更好地修复了错误)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接