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 的定义
由上图可知,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?