CPython源码学习5:字节对象

字节的结构体 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_EXTRA
    Py_ssize_t ob_refcnt; 			// 引用计数 8字节
    struct _typeobject *ob_type; 	// 类型 8字节
    Py_ssize_t ob_size;				// 对象包含的元素数量,即字节序列的长度
    Py_hash_t ob_shash;				// 哈希值,如果哈希值尚未计算,通常会将其设置为 -1
    char 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__() # 33

s1 = 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_getattrtp_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 an
       integer argument before deferring to PyBytes_FromObject, something
       PyObject_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.c

PyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
{
    PyBytesObject *op;
    if (size < 0) {
        // 如果 len 为负数,函数会设置 PyExc_SystemError 异常并返回 NULL
        PyErr_SetString(PyExc_SystemError,
            "Negative size passed to PyBytes_FromStringAndSize");
        return NULL;
    }
    if (size == 1 && str != NULL &&
        (op = characters[*str & UCHAR_MAX]) != NULL)
    {
#ifdef COUNT_ALLOCS
        one_strings++;
#endif
        Py_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_ALLOCS
        null_strings++;
#endif
        Py_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);
    else
        op = (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.c

static void
bytes_dealloc(PyObject *op)
{
    Py_TYPE(op)->tp_free(op);
}

字节的缓存机制

字节对象的缓存是一个PyBytesObject 类型的数组,存在characters 中,长度为 256。

// Objects/bytesobject.c
#define UCHAR_MAX     0xff

static PyBytesObject *characters[UCHAR_MAX + 1];


PyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
{
    PyBytesObject *op;
    if (size < 0) {
        // 如果 len 为负数,函数会设置 PyExc_SystemError 异常并返回 NULL
        PyErr_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_ALLOCS
        one_strings++;
#endif
        Py_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) # 2581807388640

z = b'a'
id(z) # 2581807388640


a1 = 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)
  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骇客567

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值