CPython源码学习5:字节对象

news/2024/6/14 11:49:00 标签: python, c语言

字节的结构体 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 中验证。

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 调用和传参

python">"""
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''

pythonb = 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 进行创建,pythonb = 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 中验证 :

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)

http://www.niftyadmin.cn/n/5514287.html

相关文章

【代码随想录】【算法训练营】【第35天】 [1005]K次取反后最大化的数组和 [134]加油站 [135]分发糖果

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 35&#xff0c;连休两天~ 题目详情 [1005] K次取反后最大化的数组和 题目描述 1005 K次取反后最大化的数组和 解题思路 前提&#xff1a;数组 思路&#xff1a;优先负数取反&#xff0c;未…

C# —— 显示转换

显示转换: 通过一些方法可以将其他数据类型转换为我们想要的数据类型 1.括号强转 作用: 一般情况下 将高精度的类型转换为低精度 // 语法: 变量类型 变量名 (转换的变量类型名称) 变量; // 注意: 精度问题 范围问题 sbyte sb 1; short s 1; int …

Day48 代码随想录打卡|二叉树篇---合并二叉树

题目&#xff08;leecode T617&#xff09;&#xff1a; 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新…

【管理咨询宝藏124】通过BLM打通前端业务与财务的双轨制设计方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏124】通过BLM打通前端业务与财务的双轨制设计方案 【格式】PDF版本 【关键词】BLM、组织架构设计、流程优化 【核心观点】 - 运用“拉通业务财务…

C++习题精选(4)—— 栈

目录 1. 最小栈2. 栈的压入弹出序列3. 逆波兰表达式求值 1. 最小栈 题目描述&#xff1a;设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素…

c++入门必备基础

学习内容&#xff1a; 一.命名空间&#xff08;namespace&#xff09; 首先了解三个作用域&#xff1a; &#xff08;1&#xff09;&#xff1a;局部域 &#xff08;2&#xff09;&#xff1a;全局域 &#xff08;3&#xff09;&#xff1a;命名空间域 命名空间是一种避免…

实验三、拓扑布局和建立小型网络《计算机网络》

假期制定的各种计划但凡实施了一点&#xff0c;也不至于一点都没有实施。 目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 1. 正确识别网络中使用的电缆线&#xff1b; 2. 为点对点网络和交换网络实施物理布线&#xff1b; 3. 验证每个网络的基本连通性。…

第二章:SQL的运算法则

法则1-1 算数运算符是对其两边的列或者值进行运算的符号 括号可以提升优先级 NULL的运算&#xff0c;结果是NULL 比较运算符是判断列或者值是否相等&#xff0c;比较大小 判断是否为NULL使用 IS NULL 或者 IS NOT NULL SELECT子句中可以使用常数或者表达式 select Pro*2 from P…