cpython 源码分析 PyBytesObject(bytes)

PyBytesObject

本文参考的是 3.8.0a0 版本的代码,详见  cpython 源码分析 基本篇

 

我们都知道在 python 中有以下两个对象可以表示 字节(bytes)

a = bytes(b"\x1cccc")
print(a) # b'\x1cccc'

b = bytearray(b"\x1cccc")
print(b) # bytearray(b'\x1cccc')

a[0] = 97 # TypeError: 'bytes' object does not support item assignment
b[0] = 97 # no problem
print(b) # bytearray(b'accc')

一个是 bytearray, 一个是 bytes. 从上面的代码里面可以发现,bytearray 是支持 [] 操作的,你可以更改任意的一个位置的值,在另一篇里也介绍过,就不多说了

而另一个是 bytes, bytes 和 tuple 一样都是 immutable 的对象,你不能像 bytearray 一样更改里面的值,bytes 里面每个位置存的都是 0 - 255 之间的数值,也就是 1个 bytes 能表示的范围

那么我们来看下 PyBytesObject 的定义

cpython 源码分析 PyBytesObject(bytes)

 由上图可知,PyBytesObject 主要由以下的几个部分构成

PyObject_VAR_HEAD: python 可变对象的公共表示部分

ob_shash: 这个 bytes 对象里面存储的字节数组的哈希值,存储这个值可以避免每次需要的时候都去重新计算,提高效

ob_sval: 存储Bytes 里的字节的真实的位置,并且最后位置为 0,有点像 c 里面的 null terminated string

 

_PyBytesWriter

bytes 本质上就是一个字节数组,在 byteobject.c 和 bytesobject.h 中并未看到比较特殊的地方

但是在 bytesobject.h 中找到了一个对象叫 _PyBytesWriter

/*
Include/bytesobject.h
144 - 165 行
*/
typedef struct {
    /* 如果 use_bytearray 为 1,这个 buffer 将会是 bytearray,
       否则 这个 buffer 就是空指针 
    */
    PyObject *buffer;

    /* 当前这个 buffer 分配的空间 */
    Py_ssize_t allocated;

    /* 需要分配的最小字节数,可以通过 _PyBytesWriter_Prepare() 函数增加 */
    Py_ssize_t min_size;

    /* 如果这个值不为0,则会使用 bytearray 作为 buffer 而不是下面的 small_buffer */
    int use_bytearray;

    /* 如果不是0,则会分配比初始值更多的空间,当 use_bytearray 不为 0 的时候,这个值必须为 0 */
    int overallocate;

    /* 在 stack 里面创建这个缓冲区 */
    int use_small_buffer;
    char small_buffer[512];
} _PyBytesWriter;

参考 Objects/byteobject.c 第 181 行 我们发现,在做字符串的格式化的时候,会创建一个 _PyBytesWriter writer 用来存储格式化好的数据

/* Objects/byteobject.c
181 - 362 行
*/
PyObject *
PyBytes_FromFormatV(const char *format, va_list vargs)
{
    /* format 是字符串格式,vargs 是那一堆字符串的列表,比如 b"%d %.2f" % (22, 3.2) */
    char *s;
    const char *f;
    const char *p;
    Py_ssize_t prec;
    int longflag;
    int size_tflag;

    char buffer[21];
    /* 初始化一个 _PyBytesWriter 对象,叫做 writer */
    _PyBytesWriter writer;
    _PyBytesWriter_Init(&writer);
    /* 给 writer 分配 format 这么长的空间,并让 s 指向这段空间 */
    s = _PyBytesWriter_Alloc(&writer, strlen(format));
    if (s == NULL)
        return NULL;
    writer.overallocate = 1;

#define WRITE_BYTES(str) \
    do { \
        s = _PyBytesWriter_WriteBytes(&writer, s, (str), strlen(str)); \
        if (s == NULL) \
            goto error; \
    } while (0)

    for (f = format; *f; f++) {
        if (*f != '%') {
            *s++ = *f;
            continue;
        }
        /* s 前一个值为 %,下一个为空
           并且 p 指向 % 的后一个位置
        */ 
        p = f++;

        /* 跳过宽度格式,比如 "%10s" 把 10 跳过 */
        while (Py_ISDIGIT(*f))
            f++;

        /* 解析精度,比如 "%.10s" 表示保留小数点后10 */
        prec = 0;
        if (*f == '.') {
            f++;
            for (; Py_ISDIGIT(*f); f++) {
                prec = (prec * 10) + (*f - '0');
            }
        }

        /* 跳过余下的非字母字符 */
        while (*f && *f != '%' && !Py_ISALPHA(*f))
            f++;

        /* 处理 “l” 标志,但是只处理 "%ld" 和 "%lu" */
        longflag = 0;
        if (*f == 'l' && (f[1] == 'd' || f[1] == 'u')) {
            longflag = 1;
            ++f;
        }

        /* 处理 “z” 标志 */
        size_tflag = 0;
        if (*f == 'z' && (f[1] == 'd' || f[1] == 'u')) {
            size_tflag = 1;
            ++f;
        }

        /* 减掉 格式化字符串占用的空间,比如 "%s" 则减 2 */
           (ex: 2 for "%s") */
        writer.min_size -= (f - p + 1);

        switch (*f) {
        /* 开始处理真正的内容 */
        case 'c':
        {
            /* 单个字符 */
            int c = va_arg(vargs, int);
            if (c < 0 || c > 255) {
                PyErr_SetString(PyExc_OverflowError,
                                "PyBytes_FromFormatV(): %c format "
                                "expects an integer in range [0; 255]");
                goto error;
            }
            writer.min_size++;
            *s++ = (unsigned char)c;
            break;
        }

        case 'd':
            /* 数字 */
            if (longflag)
                sprintf(buffer, "%ld", va_arg(vargs, long));
            else if (size_tflag)
                sprintf(buffer, "%" PY_FORMAT_SIZE_T "d",
                    va_arg(vargs, Py_ssize_t));
            else
                sprintf(buffer, "%d", va_arg(vargs, int));
            assert(strlen(buffer) < sizeof(buffer));
            WRITE_BYTES(buffer);
            break;

        case 'u':
            /* 无符号数字 */
            if (longflag)
                sprintf(buffer, "%lu",
                    va_arg(vargs, unsigned long));
            else if (size_tflag)
                sprintf(buffer, "%" PY_FORMAT_SIZE_T "u",
                    va_arg(vargs, size_t));
            else
                sprintf(buffer, "%u",
                    va_arg(vargs, unsigned int));
            assert(strlen(buffer) < sizeof(buffer));
            WRITE_BYTES(buffer);
            break;

        case 'i':
            /* 数字,老式写法,不支持 l 和 z 的格式 */
            sprintf(buffer, "%i", va_arg(vargs, int));
            assert(strlen(buffer) < sizeof(buffer));
            WRITE_BYTES(buffer);
            break;

        case 'x':
            /* 十六进制数字 */
            sprintf(buffer, "%x", va_arg(vargs, int));
            assert(strlen(buffer) < sizeof(buffer));
            WRITE_BYTES(buffer);
            break;

        case 's':
        {
            /* 字符串 */
            Py_ssize_t i;

            p = va_arg(vargs, const char*);
            i = strlen(p);
            if (prec > 0 && i > prec)
                i = prec;
            s = _PyBytesWriter_WriteBytes(&writer, s, p, i);
            if (s == NULL)
                goto error;
            break;
        }

        case 'p':
            /* 指针 */
            sprintf(buffer, "%p", va_arg(vargs, void*));
            assert(strlen(buffer) < sizeof(buffer));
            /* %p is ill-defined:  ensure leading 0x. */
            if (buffer[1] == 'X')
                buffer[1] = 'x';
            else if (buffer[1] != 'x') {
                memmove(buffer+2, buffer, strlen(buffer)+1);
                buffer[0] = '0';
                buffer[1] = 'x';
            }
            WRITE_BYTES(buffer);
            break;

        case '%':
            /* 到下一个 % */
            writer.min_size++;
            *s++ = '%';
            break;

        default:
            if (*f == 0) {
                /* fix min_size if we reached the end of the format string */
                writer.min_size++;
            }

            /* invalid format string: copy unformatted string and exit */
            WRITE_BYTES(p);
            /* 结束了,把 writer 里面之前写进去的复制一份出来 */
            return _PyBytesWriter_Finish(&writer, s);
        }
    }

#undef WRITE_BYTES

    return _PyBytesWriter_Finish(&writer, s);

 error:
    _PyBytesWriter_Dealloc(&writer);
    return NULL;
}

参考文献

How can one print a size_t variable portably using the printf family?