I think that the AttributeError
message in this Python session
>>> class A: pass
...
>>> A().x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'
is implemented in the function _PyObject_GenericGetAttrWithDict
at these lines in CPython:
if (!suppress) {
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
}
However I cannot find where the AttributeError
message in this Python session
>>> class A: __slots__ = ('x',)
...
>>> A().x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: x
is implemented in CPython. Could you provide a link to the exact lines in the GitHub repository?
It's here:
case T_OBJECT_EX:
v = *(PyObject **)addr;
if (v == NULL)
PyErr_SetString(PyExc_AttributeError, l->name);
Py_XINCREF(v);
break;
That's an excerpt from PyMember_GetOne
, which is where most of the logic for the __get__
method of the slot descriptor is implemented.
You might also be interested in how that descriptor gets there. type.__new__
internally translates __slots__
to an array of PyMemberDef
structs, one of the mechanisms classes written in C use to define access to their internal data. All PyMemberDef
structs generated this way are marked T_OBJECT_EX
, meaning they correspond to a PyObject *
in the instance memory layout, and if the pointer is null, access should raise an AttributeError. PyType_Ready
then generates the slot descriptors based on the PyMemberDef
array.