楔子
介绍完 bytes 对象在底层的数据结构之后,我们来研究一下它支持的操作,由于操作定义在类型对象中,显然我们需要查看 bytes 类型。
bytes 类型
根据解释器 API 的命名规则,bytes 类型在底层应该对应 PyBytes_Type。
// Object/bytesobject.c
PyTypeObject PyBytes_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"bytes",
PyBytesObject_SIZE,
sizeof(char),
// ...
&bytes_as_number, /* tp_as_number */
&bytes_as_sequence, /* tp_as_sequence */
&bytes_as_mapping, /* tp_as_mapping */
// ...
(richcmpfunc)bytes_richcompare, /* tp_richcompare */
// ...
};
实例对象的大小信息保存在类型对象中,由 tp_basicsize 和 tp_itemsize 负责维护。
tp_basicsize:实例对象的基本大小,对于 bytes 对象来说就是 PyBytesObject_SIZE;
tp_itemsize:如果实例对象是变长对象,并且结构体本身还保存了具体的元素,那么该字段则表示每个元素的大小,否则为 0。对于 bytes 对象来说就是 sizeof(char),即 1 字节;
然后看一下方法簇,我们发现这三个方法簇 bytes 对象居然都支持。
// Object/bytesobject.c
static PyNumberMethods bytes_as_number = {
0, /*nb_add*/
0, /*nb_subtract*/
0, /*nb_multiply*/
bytes_mod, /*nb_remainder*/
};
static PySequenceMethods bytes_as_sequence = {
// 计算长度,如 len(b"abc")
(lenfunc)bytes_length, /*sq_length*/
// 将两个 bytes 对象相加
// 如 b"abc" + b"def"
(binaryfunc)bytes_concat, /*sq_concat*/
// 将 bytes 对象重复 N 次
// 如 b"abc" * 3
(ssizeargfunc)bytes_repeat, /*sq_repeat*/
// 基于索引获取 bytes 对象的指定元素
// 如 b"abc"[1]
(ssizeargfunc)bytes_item, /*sq_item*/
0, /*sq_slice*/
0, /*sq_ass_item*/
0, /*sq_ass_slice*/
// 判断 bytes 对象是否包含某个元素
(objobjproc)bytes_contains /*sq_contains*/
};
static PyMappingMethods bytes_as_mapping = {
// 获取 bytes 对象的长度
(lenfunc)bytes_length,
// 基于切片截取 bytes 对象
(binaryfunc)bytes_subscript,
0,
};
对于数值型操作,bytes 对象实现了 nb_remainder,它居然支持求余数,这是怎么回事?好吧,我表现的有些刻意了,其实就是一个字节串的格式化操作。
对于序列型操作,bytes 对象支持五个,从名字上看也知道是做什么的,不过这些我们一会儿都会说。
对于映射型操作,bytes 对象支持两个。虽然 bytes 对象不是字典,但解释器允许它做一些类似于映射的操作,这种设计使得 bytes 对象具有更加丰富和灵活的操作接口。
下面我们就来详细介绍这些操作的底层实现。
bytes 对象的格式化
bytes 对象借用取模运算符 % 实现格式化,它对应 bytes_as_number->nb_remainder 字段,该字段被赋值为 bytes_mod。
// Object/bytesobject.c
static PyObject *
bytes_mod(PyObject *self, PyObject *arg)
{
if (!PyBytes_Check(self)) {
Py_RETURN_NOTIMPLEMENTED;
}
// PyBytes_AS_STRING 会返回 PyBytesObject 的 ob_sval 字段
// PyBytes_GET_SIZE 会返回 PyBytesObject 的 ob_size 字段,即长度
// arg 是传递的参数,它是一个元组
return _PyBytes_FormatEx(PyBytes_AS_STRING(self),
PyBytes_GET_SIZE(self),
arg, 0);
}
最终会调用 _PyBytes_FormatEx 进行格式化,我们再以 Python 为例。
info = b"name: %s, age: %d"
print(info % (b"satori", 17))
"""
b'name: satori, age: 17'
"""
bytes 对象的格式化,在工作中其实用的不多。
计算 bytes 对象的长度
计算字节串的长度会执行 bytes_as_sequence->sq_length,该字段被赋值为 bytes_length。
// Object/bytesobject.c
static Py_ssize_t
bytes_length(PyBytesObject *a)
{
return Py_SIZE(a);
}
Py_SIZE 是一个宏,我们之前说过,它会返回对象的 ob_size。
name = b"satori"
# 直接返回 ob_size
print(len(name)) # 6
ob_size 维护了 bytes 对象的字节个数,计算长度的时候直接返回。