Slots are writable by default:
>>> class A: __slots__ = ('x',)
...
>>> list(vars(A))
['__module__', '__slots__', 'x', '__doc__']
>>> vars(A)['x']
<member 'x' of 'A' objects>
>>> a = A()
>>> a.x = 'foo'
>>> del a.x
How to create read-only slots, like the slots '__thisclass__'
, '__self__'
, and '__self_class__'
of the class super
?
>>> list(vars(super))
['__repr__', '__getattribute__', '__get__', '__init__', '__new__',
'__thisclass__', '__self__', '__self_class__', '__doc__']
>>> vars(super)['__thisclass__']
<member '__thisclass__' of 'super' objects>
>>> s = super(int, 123)
>>> s.__thisclass__ = float
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: readonly attribute
>>> del s.__thisclass__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: readonly attribute
You can't, there is no option from Python code to create read-only descriptors like the ones used for __thisclass__
, etc.
In the C API, slots all use the same descriptor object type, which become read-only for entries in PyMemberDef
arrays that have flags
set to READONLY
.
E.g. the super
descriptors you identified are defined in the super_members
array:
static PyMemberDef super_members[] = {
{"__thisclass__", T_OBJECT, offsetof(superobject, type), READONLY,
"the class invoking super()"},
{"__self__", T_OBJECT, offsetof(superobject, obj), READONLY,
"the instance invoking super(); may be None"},
{"__self_class__", T_OBJECT, offsetof(superobject, obj_type), READONLY,
"the type of the instance invoking super(); may be None"},
{0}
};
When __slots__
is being processed, there is no path where you can set this flag; the code in type.__new__
just sets the first three values, which are name
, type
and offset
, respectively:
mp = PyHeapType_GET_MEMBERS(et);
slotoffset = base->tp_basicsize;
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 *);
}
}
For reference:
PyHeapType_GET_MEMBERS
accesses the PyMemberDef
array for the new type object being created. It has the right number of slots allocated already.et->ht_slots
is the tuple of slot names.slotoffset
is the relative offset into the instance object memory area to store the slot contents.T_OBJECT_EX
value for the type
field means the slot stores a pointer to a Python object, and if the pointer is set to NULL
an AttributeError
is raised when you try to get the value.Note that if it was possible to set these slots to read-only, you'd also need a mechanism to provide their value before creating a new instance! After all, if all Python code was prevented from setting a readonly attribute on instances, how would your Python class ever get to set the initial value?