To me, _PyType_Lookup sounds like a special case -- a useful special case, but it might be good to consider in together with more variations. Off the top of my head, things that you might want to enable/disable are:
(not) looking in the instance, direct class, superclasses, metaclass
(not) invoking descriptors
(not) binding methods with Py_TPFLAGS_METHOD_DESCRIPTOR
Also: when we walk the MRO we should return the class the attribute is on (the defining class).
I guess the most important thing about this lookup is not the exact behaviour (though it's important to document that), but the fact that Python uses it to find (and cache) special methods.
Should we expose this, or _PyObject_LookupSpecial or _PyObject_LookupSpecialMethod?
Do we know how many uses those functions have, and how many of the _PyType_Lookup users resolve descriptors after the _PyType_Lookup call?
PyObject*descr=_PyType_Lookup(type, name);
if (descr!=NULL) {
descrgetfuncget=Py_TYPE(descr)->tp_descr_get;
descrsetfuncset=Py_TYPE(descr)->tp_descr_set;
if (get!=NULL&&set!=NULL) {
Py_INCREF(descr);
val=get(descr, iter->obj, (PyObject*)type);
Py_DECREF(descr);
if (val!=NULL) goto found_val;
PyErr_Clear();
}
}
pythonnet (src/runtime/Types/MetaType.cs):
publicstaticinttp_setattro(BorrowedReferencetp, BorrowedReferencename, BorrowedReferencevalue)
{
BorrowedReferencedescr=Runtime._PyType_Lookup(tp, name);
if (descr!=null)
{
BorrowedReferencedt=Runtime.PyObject_TYPE(descr);
if (dt==Runtime.PyWrapperDescriptorType||dt==Runtime.PyMethodType||typeof(ExtensionType).IsInstanceOfType(GetManagedObject(descr))
)
{
IntPtrfp=Util.ReadIntPtr(dt, TypeOffset.tp_descr_set);
if (fp!=IntPtr.Zero)
{
returnNativeCall.Int_Call_3(fp, descr, name, value);
}
Exceptions.SetError(Exceptions.AttributeError, "attribute is read-only");
return-1;
}
}
intres=Runtime.PyObject_GenericSetAttr(tp, name, value);
Runtime.PyType_Modified(tp);
returnres;
}
sentencepiece: code generated by SWIG.
#ifdefSWIGPYTHON_BUILTINSWIGRUNTIMEintSWIG_Python_NonDynamicSetAttr(PyObject*obj, PyObject*name, PyObject*value) {
...
descr=_PyType_Lookup(tp, name);
f=NULL;
if (descr!=NULL)
f=descr->ob_type->tp_descr_set;
if (!f) {
if (PyString_Check(name)) {
encoded_name=name;
SWIG_Py_INCREF(name);
} else {
encoded_name=PyUnicode_AsUTF8String(name);
if (!encoded_name)
goto done;
}
PyErr_Format(PyExc_AttributeError, "'%.100s' object has no attribute '%.200s'", tp->tp_name, PyString_AsString(encoded_nameSWIG_Py_DECREF(encoded_name);
} else {
res=f(descr, obj, value);
}
yappi:
// get name from type+namemo=_PyType_Lookup((PyTypeObject*)obj_type, name);
Py_XINCREF(mo);
...
if (mo!=NULL) {
pit->name=PyObject_Repr(mo);
Py_DECREF(mo);
returnpit;
}
The C API Working Group decided to not add int PyType_Lookup(PyTypeObject *type, PyObject *name, PyObject **attr) API: capi-workgroup/decisions#83. So I close the issue. For now, the status quo is to keep the private _PyType_Lookup() function.
Activity
pythongh-139847: Add PyType_Lookup() function
pythongh-139847: Add PyType_Lookup() function
markshannon commented on Oct 10, 2025
This does seem to be filling a gap in the C API.
encukou commented on Oct 10, 2025
To me, _PyType_Lookup sounds like a special case -- a useful special case, but it might be good to consider in together with more variations. Off the top of my head, things that you might want to enable/disable are:
Also: when we walk the MRO we should return the class the attribute is on (the defining class).
encukou commented on Oct 10, 2025
I had some notes lying around; so I collected them here: https://discuss.python.org/t/api-for-special-attribute-lookup/104288
encukou commented on Oct 20, 2025
I guess the most important thing about this lookup is not the exact behaviour (though it's important to document that), but the fact that Python uses it to find (and cache) special methods.
Should we expose this, or _PyObject_LookupSpecial or _PyObject_LookupSpecialMethod?
Do we know how many uses those functions have, and how many of the _PyType_Lookup users resolve descriptors after the _PyType_Lookup call?
vstinner commented on Dec 3, 2025
I looked at some projects to see how _PyType_Lookup() is used.
mypyc: C code emitted by mypyc:
msgspec:
pythonnet (src/runtime/Types/MetaType.cs):
sentencepiece: code generated by SWIG.
yappi:
vstinner commented on Feb 10, 2026
The C API Working Group decided to not add int PyType_Lookup(PyTypeObject *type, PyObject *name, PyObject **attr) API: capi-workgroup/decisions#83. So I close the issue. For now, the status quo is to keep the private _PyType_Lookup() function.