我只想更深入地介绍nneonneos 的答案(我很好奇事情是如何在幕后工作的)。
主要区别已在以下文档中提到bytearray
:
返回一个新的字节数组。bytearray 类型是0 <= x < 256 范围内的可变整数序列。它具有可变序列的大多数常用方法,在可变序列类型中描述,以及字节类型具有的大多数方法,请参见字节和字节数组方法。
另一方面bytes
是不可变的:
返回一个新的“bytes”对象,它是0 <= x < 256 范围内的不可变整数序列。bytes 是 bytearray 的不可变版本——它具有相同的非变异方法以及相同的索引和切片行为。
您可以在 CPython 中轻松演示这一点,其中id()
返回对象的地址:
CPython 实现细节:这是内存中对象的地址。
当你扩展bytes()
你总是得到新的对象:
>>> a = bytes(b'abc')
>>> id(a)
52845824
>>> a += b'de'
>>> id(a)
52843384
而bytearray()
仍然是具有不同属性的相同对象
>>> b = bytearray(b'abc')
>>> id(b)
52786352
>>> b += b'de'
>>> id(b)
52786352
我想看看这在内部看起来如何,所以dis
派上用场了:
>>> def iadd(a, b):
... a += b
... return a
...
>>> dis.dis(iadd)
2 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 INPLACE_ADD
7 STORE_FAST 0 (a)
3 10 LOAD_FAST 0 (a)
13 RETURN_VALUE
所以我想最后 C 路由PyNumber_InPlaceAdd
被调用,所以让我们启动 gdb。
字节
(gdb) b PyNumber_InPlaceAdd
Breakpoint 1 at 0x5317a7: file Objects/abstract.c, line 1066.
(gdb) c
>>> a = b'abc'
>>> a += b'de'
Breakpoint 1, PyNumber_InPlaceAdd (v=0xb030f0, w=0xb03040) at Objects/abstract.c:1066
1066 PyObject *result = binary_iop1(v, w, NB_SLOT(nb_inplace_add),
(gdb) s
binary_iop1 (v=0xb030f0, w=0xb03040, iop_slot=152, op_slot=0) at Objects/abstract.c:1010
1010 PyNumberMethods *mv = v->ob_type->tp_as_number;
(gdb) s
1011 if (mv != NULL) {
(gdb) info locals
mv = 0x0
(gdb)
1021 return binary_op1(v, w, op_slot);
(gdb)
binary_op1 (v=0xb030f0, w=0xb03040, op_slot=0) at Objects/abstract.c:765
...
(gdb)
796 return Py_NotImplemented;
PyNumber_InPlaceAdd (v=0xb030f0, w=0xb03040) at Objects/abstract.c:1068
1068 if (result == Py_NotImplemented) {
(gdb)
1069 PySequenceMethods *m = v->ob_type->tp_as_sequence;
(gdb)
1070 Py_DECREF(result);
(gdb) info locals
m = 0x856940 <bytes_as_sequence>
(gdb) s
1071 if (m != NULL) {
(gdb)
1072 binaryfunc f = NULL;
(gdb)
1073 f = m->sq_inplace_concat;
(gdb)
1074 if (f == NULL)
(gdb)
1075 f = m->sq_concat;
(gdb)
1076 if (f != NULL)
(gdb)
1077 return (*f)(v, w);
(gdb) info locals f
f = 0x54948c <bytes_concat>
所以最后bytes_concat
被调用并复制内存:
size = va.len + vb.len;
if (size < 0) {
PyErr_NoMemory();
goto done;
}
result = PyBytes_FromStringAndSize(NULL, size);
if (result != NULL) {
memcpy(PyBytes_AS_STRING(result), va.buf, va.len);
memcpy(PyBytes_AS_STRING(result) + va.len, vb.buf, vb.len);
}
字节数组
它有类似的流程(也以 结束PyNumber_InPlaceAdd
):
>>> b = bytearray(b'abc')
[57107 refs]
>>> b += b'de'
Breakpoint 1, PyNumber_InPlaceAdd (v=0xa35b80, w=0xb03040) at Objects/abstract.c:1066
1066 PyObject *result = binary_iop1(v, w, NB_SLOT(nb_inplace_add),
(gdb) n
1068 if (result == Py_NotImplemented) {
(gdb) s
1069 PySequenceMethods *m = v->ob_type->tp_as_sequence;
(gdb)
1070 Py_DECREF(result);
(gdb)
1071 if (m != NULL) {
(gdb)
1072 binaryfunc f = NULL;
(gdb)
1073 f = m->sq_inplace_concat;
(gdb)
1074 if (f == NULL)
(gdb) info locals
f = 0x537901 <bytearray_iconcat>
(gdb) n
1076 if (f != NULL)
(gdb)
1077 return (*f)(v, w);
bytes
指针m->sq_inplace_concat
为空,因此被m->sq_concat
调用,bytearray
已设置,因此bytearray_iconcat
被调用并以:
if (size < self->ob_alloc) {
Py_SIZE(self) = size;
PyByteArray_AS_STRING(self)[Py_SIZE(self)] = '\0'; /* Trailing null byte */
}
else if (PyByteArray_Resize((PyObject *)self, size) < 0) {
PyBuffer_Release(&vo);
return NULL;
}
memcpy(PyByteArray_AS_STRING(self) + mysize, vo.buf, vo.len);
因此,速度差异(在我的情况下,我需要bytes
从 80000+ 块中构建一个,bytes
并且需要 180 秒,bytearray
0.260)。