长亭百川云 - 文章详情

bytes 对象都支持哪些操作,它们是怎么实现的?

古明地觉的编程教室

33

2024-07-13

楔子

介绍完 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 对象的字节个数,计算长度的时候直接返回。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2