# bytes
# str&bytes
# 内部结构
- 注意,CPython会为待存储的字节序列额外分配一字节空间用于在末尾处保存
\0,以便兼容C字符串。
typedef struct {
PyObject_VAR_HEAD // 24 字节
Py_hash_t ob_shash; // 8 字节
char ob_sval[1]; // n+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;
# 对象行为
PyTypeObject PyBytes_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"bytes",
PyBytesObject_SIZE,
sizeof(char),
// ...
&bytes_as_number, /* tp_as_number */
&bytes_as_sequence, /* tp_as_sequence */
&bytes_as_mapping, /* tp_as_mapping */
(hashfunc)bytes_hash, /* tp_hash */
// ...
};
# 数值型
static PyNumberMethods bytes_as_number = {
0, /*nb_add*/
0, /*nb_subtract*/
0, /*nb_multiply*/
bytes_mod, /*nb_remainder*/ // 根据源码可以看出bytes只是利用%运算实现字符串格式化
// 例如 b'msg: a=%d b=%d' % (1, 2)
}
# bytes_mod
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);
}
# 序列型
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*/ // 包含关系判断
};
# bytes_length
static Py_ssize_t
bytes_length(PyBytesObject *a)
{
return Py_SIZE(a);
}
# bytes_concat
- 注意:如果是
a+b+c这样的形式,实际上执行的是a+b=r和r+c两步,即a,b实际上需要被,待合并的bytes对象越多,数据重复拷贝的现象也就越严重。 - 因此大多类似的情况都会提供一个join方法来高效合并多个序列化对象;join会先遍历待合并对象计算总长度,然后根据总长度来创建目标对象,最后再遍历待合并对象注意拷贝数据,以此解决重复拷贝的陷阱。
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;
}
# 优化
# 字符缓冲池
为了优化单字节bytes对象(也可称为字符对象)的创建效率,Python内部维护了一个字符缓冲池,长度为256。
Python内部创建单字节bytes对象时,会先检查对象是否已在缓冲池中,如果在则直接返回。
因此需要注意下列代码的结果
# 场景一 >>> a1 = b'a' >>> a2 = b'a' >>> a1 is a2 True # 场景二 >>> ab1 = b'ab' >>> ab2 = b'ab' >>> ab1 is ab2 False # 场景3 py文件中 # 此部分原因与代码作用域和虚拟机有关系 ab1 = b'ab' ab2 = b'ab' print(ab1 is ab2) # True
static PyBytesObject *characters[UCHAR_MAX + 1]; // 256个
PyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
{
PyBytesObject *op;
if (size < 0) {
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;
}
op = (PyBytesObject *)_PyBytes_FromSize(size, 0);
if (op == NULL)
return NULL;
if (str == NULL)
return (PyObject *) op;
memcpy(op->ob_sval, str, size);
/* share short strings */
if (size == 1) {
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
}
return (PyObject *) op;
}
# 缓存hash值
计算hash值是一个比较耗时的工作,而且bytes是一个不可更改对象-其hash值从创建那一刻就已经确定,因此CPython会将其hash值存储在对象内,而不是每次取的时候都重新计算,以空间换时间。
类似会缓存hash值的还有str。
下图中ob_hash字段即位缓存的hash值。
估计不少不可变对象都会做这种优化。