考虑到我有数百万个对象,其中 3__slots__
x
像这样的短插槽名称与长像这样的插槽名称相比,内存效率更高would_you_like_fries_with_that_cheeseburger
吗?
还是每个类只分配一次名称(而不是每个实例一次?)
考虑到我有数百万个对象,其中 3__slots__
x
像这样的短插槽名称与长像这样的插槽名称相比,内存效率更高would_you_like_fries_with_that_cheeseburger
吗?
还是每个类只分配一次名称(而不是每个实例一次?)
插槽的名称只占用每个类的内存,而不是每个实例。
插槽使用直接映射到为实例保留的内存的描述符,属性名称映射到类上的这些描述符。
因此,名称的长度不会影响每个实例用于插槽的内存量;名称仅在__dict__
类的属性中(将名称映射到描述符)和描述符对象本身(以提供对象的字符串表示)中占用空间;该字符串甚至被拘留。
您可以检查自定义描述符在C 源代码中type.__new__()
的状态(负责创建类对象):
if (et->ht_slots != NULL) {
for (i = 0; i < nslots; i++, mp++) {
mp->name = PyUnicode_AsUTF8(
PyTuple_GET_ITEM(et->ht_slots, i));
if (mp->name == NULL)
goto error;
mp->type = T_OBJECT_EX;
mp->offset = slotoffset;
/* __dict__ and __weakref__ are already filtered out */
assert(strcmp(mp->name, "__dict__") != 0);
assert(strcmp(mp->name, "__weakref__") != 0);
slotoffset += sizeof(PyObject *);
}
}
mp->offset
实例的内存索引在哪里。
使用的描述符是一个PyMemberDescr_Type
对象,其member_get
函数使用(非常通用的)PyMember_GetOne()
函数;使用偏移量从实例中检索指针:
PyMember_GetOne(const char *addr, PyMemberDef *l)
{
PyObject *v;
addr += l->offset;
addr
是实例的内存地址。该函数的其余部分处理各种类型的成员;slot 成员始终设置为 type T_OBJECT_EX
:
case T_OBJECT_EX:
v = *(PyObject **)addr;
if (v == NULL)
PyErr_SetString(PyExc_AttributeError, l->name);
Py_XINCREF(v);
break;
然后函数返回;如果从未设置该属性(因此v == NULL
),AttributeError
则会引发异常。