-
-
Notifications
You must be signed in to change notification settings - Fork 34.2k
Description
Bug report
Bug description:
When applying object.__getattribute__(cls, attr) to a type object, builtin class behave differently from custom class (or instances of class), i.e.
object.__getattribute__(builtin_class, attr)
behaves differently from
object.__getattribute__(custom_class, attr),object.__getattribute__(instance_of_class, attr)
It seems like that object.__getattribute__(builtin_class, attr) doesn't lookup its own dict of builtin class before checking up dict of its metaclass' mro.
# builtin class case
assert '__mul__' in vars(int)
object.__getattribute__(int, "__mul__")
Out: AttributeError: 'type' object has no attribute '__mul__'
# custom class case
class A(int):
def __mul__(self, *args, **kwargs):
return super().__mul__(*args, **kwargs)
assert '__mul__' in vars(A)
object.__getattribute__(A, "__mul__")
Out: <function __main__.A.__mul__(self, *args, **kwargs)> After diving into dict checking part of _PyObject_GenericGetAttrWithDict() (v3.14), since metaclass of builtin class is type, which hasn't turn on Py_TPFLAGS_INLINE_VALUES and Py_TPFLAGS_MANAGED_DICT, so the dicts of builtin class or custom class are all treated as computed_dict by dictptr = _PyObject_ComputedDictPointer(obj);. The problem is that the dict of builtin class turns out to be the managed_dict, their tp_dict slot always point to NULL, so it seems like skipping check of their own tp_dict, and finally lead to this kind of inconsistent behavior.
The primitive fix is below, and it works on v3.14
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1733,6 +1733,21 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
dict = (PyObject *)_PyObject_GetManagedDict(obj);
}
+ else if (tp->tp_flags & Py_TPFLAGS_TYPE_SUBCLASS) {
+ PyTypeObject * obj_as_type = _Py_CAST(PyTypeObject*, obj);
+
+ /* exactly the same as
+ * dict = lookup_tp_dict(obj_as_type) */
+ if (obj_as_type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ managed_static_type_state *state = _PyStaticType_GetState(interp, obj_as_type);
+ assert(state != NULL);
+ dict = state->tp_dict;
+ }
+ else {
+ dict = obj_as_type->tp_dict;
+ }
+ }
else {
PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
if (dictptr) {object.__getattribute__(int, "__mul__")
Out: <slot wrapper '__mul__' of 'int' objects>Since type objects are only the legitimate self argument of type.__getattribute__, it will raise an error when applied to instances of class. However since everything in Python is object, so everything is legitimate self argument of object.__getattribute__, although it is rare case when applied to type objects in routine usage. And in routine practice, dot operator and getattr() always guarantee instances use __getattribute__ of class and type objects use __getattribute___ of metaclass. So I don't know whether it worth fixing, I just point it out here.
CPython versions tested on:
3.14, 3.12
Operating systems tested on:
Linux, Windows