字节的结构体 PyBytesObject
浮点数的结构体 PyBytesObject ,定义在头文件 Include/bytesobject.h 中,包含PyObject_VAR_HEAD 说明字节是可变大小的对象。
// Include/bytesobject.h
typedef struct {PyObject_VAR_HEAD // 可变大小的对象Py_hash_t ob_shash;char ob_sval[1];/* Invariants:* ob_sval contains space for 'ob_size+1' elements.* ob_sval[ob_size] == 0.* ob_shash is the hash of the string or -1 if not computed yet.*/
} PyBytesObject;// 展开 PyObject_VAR_HEAD
typedef struct {_PyObject_HEAD_EXTRAPy_ssize_t ob_refcnt; // 引用计数 8字节struct _typeobject *ob_type; // 类型 8字节Py_ssize_t ob_size; // 对象包含的元素数量,即字节序列的长度Py_hash_t ob_shash; // 哈希值,如果哈希值尚未计算,通常会将其设置为 -1char ob_sval[1]; // 字节序列的实际数据,其长度为 1,但实际分配的内存可能大于 1,C语言中字节序列以 \0 字符结尾,计算ob_size的时候不包括\0
}
字节内存大小
把 PyBytesObject 展开后发现ob_refcnt 是整数型长度 8 字节,ob_type 是指针长度是 8 字节,ob_size 是整数型长度是 8 字节,ob_shash 是整数型长度是 8 字节,ob_sval 是 char 类型的数组长度是 n 1 字节 再加上末尾的 \0 的 1 字节,所以字节对象的大小是变长的 32+n1+1 字节,可以在 Python 中验证。
s = b"" # 空字节串长度为0 内存占用为 32 + 0*1 + 1 = 33
s.__sizeof__() # 33s1 = b'0x1715f7b01e0' # 字节长度为 13,内存占用 32 + 13 * 1 + 1 = 46
s1.__sizeof__() # 46# 另一种计算方式
import sys
sys.getsizeof(s1) # 46
字节的类型对象 PyBytes_Type
字节对象的类型的结构体是 PyBytes_Type,定义在头文件 Include/bytesobject.c 中。和其他类型对象一样,提供了多个函数来检查和操作字节对象,包括字节的创建bytes_new、字节的销毁bytes_dealloc、字节的函数集bytes_as_number 包含取余等计算、序列对象的方法集bytes_as_sequence、映射相关的方法集bytes_as_mapping、字节的哈希计算bytes_hash、用于生成字节字符串表示的函数bytes_repr 等等。
// Objects/bytesobject.c
PyTypeObject PyBytes_Type = {PyVarObject_HEAD_INIT(&PyType_Type, 0)"bytes",PyBytesObject_SIZE, // tp_basicsize 基本大小 sizeof(char), // tp_itemsize可变长对象的每个元素的大小bytes_dealloc, /* tp_dealloc */0, /* tp_print */0, /* tp_getattr */0, /* tp_setattr */0, /* tp_reserved */(reprfunc)bytes_repr, /* tp_repr */&bytes_as_number, /* tp_as_number */&bytes_as_sequence, /* tp_as_sequence */&bytes_as_mapping, /* tp_as_mapping */(hashfunc)bytes_hash, /* tp_hash */0, /* tp_call */bytes_str, /* tp_str */PyObject_GenericGetAttr, /* tp_getattro */0, /* tp_setattro */&bytes_as_buffer, /* tp_as_buffer */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |Py_TPFLAGS_BYTES_SUBCLASS, /* tp_flags */bytes_doc, /* tp_doc */0, /* tp_traverse */0, /* tp_clear */(richcmpfunc)bytes_richcompare, /* tp_richcompare */0, /* tp_weaklistoffset */bytes_iter, /* tp_iter */0, /* tp_iternext */bytes_methods, /* tp_methods */0, /* tp_members */0, /* tp_getset */&PyBaseObject_Type, /* tp_base */0, /* tp_dict */0, /* tp_descr_get */0, /* tp_descr_set */0, /* tp_dictoffset */0, /* tp_init */0, /* tp_alloc */bytes_new, /* tp_new */PyObject_Del, /* tp_free */
};
但是和浮点数类型不一样的地方,tp_basicsize 对应的是PyBytesObject_SIZE,tp_itemsize 对应的是sizeof(char)
// Objects/bytesobject.c
// 用于计算 PyBytesObject 结构体的基本大小,不包括存储在 ob_sval 字段中的实际字节数据
#define PyBytesObject_SIZE (offsetof(PyBytesObject, ob_sval) + 1)// ob_sval的类型是char的数组,所以数组中每个元素的长度为sizeof(char)
sizeof(char)
值为 0 的字段表示这些字段在初始化时没有被显式赋值或使用了默认值,即最终调用的时候使用的是 PyTypeObject 中对应的值。
- tp_print: 设为 0 表示没有定义打印函数,继承了PyTypeObject 中的打印函数。
- tp_getattr 和 tp_setattr: 设为 0 表示没有定义获取和设置属性的函数。
- tp_call: 设为 0 表示这个类型对象不能被调用(即 float 对象不是可调用的)。
- tp_init: 设为 0 表示继承了PyTypeObject 中的初始化函数。
字节创建
第一种和浮点数一样是通过类型对象创建,调用类型对象中的 tp_new 函数,字节对象对应的是 bytes_new,可以看到经过一些列的检测和判断,最终落在了 PyBytes_FromObject 函数上。
看下类型对象创建时 python 中的函数 bytes 调用和传参
"""
help(bytes)class bytes(object)| bytes(iterable_of_ints) -> bytes| bytes(string, encoding[, errors]) -> bytes| bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer| bytes(int) -> bytes object of size given by the parameter initialized with null bytes| bytes() -> empty bytes object| | Construct an immutable array of bytes from:| - an iterable yielding integers in range(256)| - a text string encoded using the specified encoding| - any object implementing the buffer API.| - an integer"""
bytes([1,2,3,4]) # b'\x01\x02\x03\x04'
bytes("1234", encoding="gbk") # b'1234'
bytes(b"1234") # b'1234'
bytes(4) # b'\x00\x00\x00\x00' 生成长度为4的 null 字节
bytes() # b''
python 中b = bytes(b"1234")
通过类型对象创建字节对象。
// Objects/bytesobject.c
static PyObject *
bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{PyObject *x = NULL;const char *encoding = NULL;const char *errors = NULL;PyObject *new = NULL;PyObject *func;Py_ssize_t size;static char *kwlist[] = {"source", "encoding", "errors", 0};_Py_IDENTIFIER(__bytes__);if (type != &PyBytes_Type)return bytes_subtype_new(type, args, kwds);if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oss:bytes", kwlist, &x,&encoding, &errors))return NULL;if (x == NULL) { // 调用bytes()时会走到这if (encoding != NULL || errors != NULL) {PyErr_SetString(PyExc_TypeError,"encoding or errors without sequence ""argument");return NULL;}return PyBytes_FromStringAndSize(NULL, 0);}if (encoding != NULL) {// 调用 bytes("123", encoding="gbk") 会走到这/* Encode via the codec registry */if (!PyUnicode_Check(x)) {PyErr_SetString(PyExc_TypeError,"encoding without a string argument");return NULL;}new = PyUnicode_AsEncodedString(x, encoding, errors);if (new == NULL)return NULL;assert(PyBytes_Check(new));return new;}if (errors != NULL) {PyErr_SetString(PyExc_TypeError,PyUnicode_Check(x) ?"string argument without an encoding" :"errors without a string argument");return NULL;}/* We'd like to call PyObject_Bytes here, but we need to check for aninteger argument before deferring to PyBytes_FromObject, somethingPyObject_Bytes doesn't do. */func = _PyObject_LookupSpecial(x, &PyId___bytes__);if (func != NULL) {new = _PyObject_CallNoArg(func);Py_DECREF(func);if (new == NULL)return NULL;if (!PyBytes_Check(new)) {PyErr_Format(PyExc_TypeError,"__bytes__ returned non-bytes (type %.200s)",Py_TYPE(new)->tp_name);Py_DECREF(new);return NULL;}return new;}else if (PyErr_Occurred())return NULL;if (PyUnicode_Check(x)) {PyErr_SetString(PyExc_TypeError,"string argument without an encoding");return NULL;}/* Is it an integer? */if (PyIndex_Check(x)) {// 调用 bytes(3) 会走到这size = PyNumber_AsSsize_t(x, PyExc_OverflowError);if (size == -1 && PyErr_Occurred()) {if (!PyErr_ExceptionMatches(PyExc_TypeError))return NULL;PyErr_Clear(); /* fall through */}else {if (size < 0) {PyErr_SetString(PyExc_ValueError, "negative count");return NULL;}new = _PyBytes_FromSize(size, 1);if (new == NULL)return NULL;return new;}}// 调用 bytes(b"123") 会走到这return PyBytes_FromObject(x);
}// 用于将一个对象转换为字节对象
// 函数尝试通过调用对象的 __bytes__ 方法或其他合适的转换方式来生成字节对象
PyObject *
PyBytes_FromObject(PyObject *x)
{PyObject *it, *result;if (x == NULL) {PyErr_BadInternalCall();return NULL;}// 检查输入对象是否已经是字节对象if (PyBytes_CheckExact(x)) {Py_INCREF(x);return x;}/* Use the modern buffer interface */if (PyObject_CheckBuffer(x))return _PyBytes_FromBuffer(x);if (PyList_CheckExact(x))return _PyBytes_FromList(x);if (PyTuple_CheckExact(x))return _PyBytes_FromTuple(x);if (!PyUnicode_Check(x)) {it = PyObject_GetIter(x);if (it != NULL) {result = _PyBytes_FromIterator(it, x);Py_DECREF(it);return result;}if (!PyErr_ExceptionMatches(PyExc_TypeError)) {return NULL;}}PyErr_Format(PyExc_TypeError,"cannot convert '%.200s' object to bytes",x->ob_type->tp_name);return NULL;
}
第二种是通过 C API 进行创建,python 中b = b"1234"
通过 C API 创建字节对象。
// Objects/bytesobject.cPyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
{PyBytesObject *op;if (size < 0) {// 如果 len 为负数,函数会设置 PyExc_SystemError 异常并返回 NULLPyErr_SetString(PyExc_SystemError,"Negative size passed to PyBytes_FromStringAndSize");return NULL;}if (size == 1 && str != NULL &&(op = characters[*str & UCHAR_MAX]) != NULL){
#ifdef COUNT_ALLOCSone_strings++;
#endifPy_INCREF(op);return (PyObject *)op;}// 使用 PyObject_Malloc 为 PyBytesObject 分配内存。// 分配的大小是 sizeof(PyBytesObject) + len,以便存储字节对象本身和它的内容op = (PyBytesObject *)_PyBytes_FromSize(size, 0);if (op == NULL)return NULL;if (str == NULL)return (PyObject *) op;// 如果 v 非 NULL,使用 memcpy 将 v 指向的字符串的前 len 个字节复制到新分配的字节对象中memcpy(op->ob_sval, str, size);/* share short strings */if (size == 1) {characters[*str & UCHAR_MAX] = op;Py_INCREF(op);}// 将新创建并初始化的 PyBytesObject 转换为 PyObject*,并返回给调用者return (PyObject *) op;
}static PyObject *
_PyBytes_FromSize(Py_ssize_t size, int use_calloc)
{PyBytesObject *op;assert(size >= 0);if (size == 0 && (op = nullstring) != NULL) {
#ifdef COUNT_ALLOCSnull_strings++;
#endifPy_INCREF(op);return (PyObject *)op;}if ((size_t)size > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) {PyErr_SetString(PyExc_OverflowError,"byte string is too large");return NULL;}/* Inline PyObject_NewVar */if (use_calloc)op = (PyBytesObject *)PyObject_Calloc(1, PyBytesObject_SIZE + size);elseop = (PyBytesObject *)PyObject_Malloc(PyBytesObject_SIZE + size);if (op == NULL)return PyErr_NoMemory();// 使用 PyObject_Init_VAR 初始化变量长度对象 (PyVarObject)(void)PyObject_INIT_VAR(op, &PyBytes_Type, size);op->ob_shash = -1;// 无论 v 是否为 NULL,都将新字节对象的末尾设置为 '\0'if (!use_calloc)op->ob_sval[size] = '\0';/* empty byte string singleton */if (size == 0) {nullstring = op;Py_INCREF(op);}// 将新创建并初始化的 PyBytesObject 转换为 PyObject*,并返回给调用者return (PyObject *) op;
}
字节对象的销毁
销毁对象是调用指针 tp_dealloc 所指的函数,字节对象对应的就是 bytes_dealloc,可以看到直接调用了tp_free(op)函数, tp_free内存释放函数指向 PyObject_Free,负责实际的内存释放。
// Objects/bytesobject.cstatic void
bytes_dealloc(PyObject *op)
{Py_TYPE(op)->tp_free(op);
}
字节的缓存机制
字节对象的缓存是一个PyBytesObject 类型的数组,存在characters 中,长度为 256。
// Objects/bytesobject.c
#define UCHAR_MAX 0xffstatic PyBytesObject *characters[UCHAR_MAX + 1];PyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
{PyBytesObject *op;if (size < 0) {// 如果 len 为负数,函数会设置 PyExc_SystemError 异常并返回 NULLPyErr_SetString(PyExc_SystemError,"Negative size passed to PyBytes_FromStringAndSize");return NULL;}// 长度为1的字节,如果在缓存中,就直接获取并返回if (size == 1 && str != NULL &&(op = characters[*str & UCHAR_MAX]) != NULL){
#ifdef COUNT_ALLOCSone_strings++;
#endifPy_INCREF(op);return (PyObject *)op;}// 使用 PyObject_Malloc 为 PyBytesObject 分配内存。// 分配的大小是 sizeof(PyBytesObject) + len,以便存储字节对象本身和它的内容op = (PyBytesObject *)_PyBytes_FromSize(size, 0);if (op == NULL)return NULL;if (str == NULL)return (PyObject *) op;// 如果 v 非 NULL,使用 memcpy 将 v 指向的字符串的前 len 个字节复制到新分配的字节对象中memcpy(op->ob_sval, str, size);/* share short strings */// 长度为1时,缓存起来if (size == 1) {characters[*str & UCHAR_MAX] = op;Py_INCREF(op);}// 将新创建并初始化的 PyBytesObject 转换为 PyObject*,并返回给调用者return (PyObject *) op;
}
缓存过程:
1、当字节长度为 1,并且不为空的时候,会缓存在characters 中
2、当初始化的时候,长度为 1 的字节,先会判断是否在缓存中,如果在缓存中就直接返回。
在 python 中验证 :
"""
两个单字节的地址一样,多字节的地址不同
"""
a = b'a'
id(a) # 2581807388640z = b'a'
id(z) # 2581807388640a1 = b"abc"
id(a1) # 1902678903424
z1 = b"abc"
id(z1) # 1902678904480
字节的属性和函数
PyBytes_Type 中还定义了一些字节对象相关的属性和函数等
bytes_hash 字节的哈希值
可以看到计算完哈希后,将哈希值也赋给了字节对象的ob_shash,因为字节对象的哈希是经常用到的,所以为了效率计算一次后,保存在对象中。
static Py_hash_t
bytes_hash(PyBytesObject *a)
{if (a->ob_shash == -1) {/* Can't fail */a->ob_shash = _Py_HashBytes(a->ob_sval, Py_SIZE(a));}return a->ob_shash;
}
bytes_as_number 结构体是所有数值的函数集,但是只包含取余函数,但是真实的函数功能并不是取余,而是用来%格式化字节串。
static PyNumberMethods bytes_as_number = {0, /*nb_add*/0, /*nb_subtract*/0, /*nb_multiply*/bytes_mod, /*nb_remainder*/};static PyObject *
bytes_mod(PyObject *self, PyObject *arg)
{if (!PyBytes_Check(self)) {Py_RETURN_NOTIMPLEMENTED;}// 格式化字节串return _PyBytes_FormatEx(PyBytes_AS_STRING(self), PyBytes_GET_SIZE(self),arg, 0);
}
bytes_as_sequence 结构体是所有序列的函数集,包含长度、连接、重复、是否包含、索引等
static PySequenceMethods bytes_as_sequence = {(lenfunc)bytes_length, /*sq_length 查看序列的长度 */(binaryfunc)bytes_concat, /*sq_concat 合并两个字节串 */(ssizeargfunc)bytes_repeat, /*sq_repeat 使用*重复多次字节 */(ssizeargfunc)bytes_item, /*sq_item 索引单个字节返回整形,切片返回字节*/0, /*sq_slice*/0, /*sq_ass_item*/0, /*sq_ass_slice*/(objobjproc)bytes_contains /*sq_contains 是否包含 in */
};
bytes_length 获取对象长度
static Py_ssize_t
bytes_length(PyBytesObject *a)
{return Py_SIZE(a);
}
bytes_concat 拼接两个字节串
static PyObject *
bytes_concat(PyObject *a, PyObject *b)
{Py_buffer va, vb;PyObject *result = NULL;va.len = -1;vb.len = -1;if (PyObject_GetBuffer(a, &va, PyBUF_SIMPLE) != 0 ||PyObject_GetBuffer(b, &vb, PyBUF_SIMPLE) != 0) {PyErr_Format(PyExc_TypeError, "can't concat %.100s to %.100s",Py_TYPE(b)->tp_name, Py_TYPE(a)->tp_name);goto done;}/* Optimize end cases */if (va.len == 0 && PyBytes_CheckExact(b)) {result = b;Py_INCREF(result);goto done;}if (vb.len == 0 && PyBytes_CheckExact(a)) {result = a;Py_INCREF(result);goto done;}if (va.len > PY_SSIZE_T_MAX - vb.len) {PyErr_NoMemory();goto done;}result = PyBytes_FromStringAndSize(NULL, va.len + vb.len);if (result != NULL) {memcpy(PyBytes_AS_STRING(result), va.buf, va.len);memcpy(PyBytes_AS_STRING(result) + va.len, vb.buf, vb.len);}done:if (va.len != -1)PyBuffer_Release(&va);if (vb.len != -1)PyBuffer_Release(&vb);return result;
}
字节相关的宏定义和函数
#define PyBytes_Check(op) \PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_BYTES_SUBCLASS)#define PyBytes_CheckExact(op) (Py_TYPE(op) == &PyBytes_Type)