diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1382,6 +1382,19 @@ See :ref:`special-lookup`. +.. method:: object.__getattribute_super__(cls, name, obj, owner) + + Called by the *__getattribute__* method of :class:`super` when defined, + by default that method looks in the *__dict__* of *cls*. + + The method should resolve attribute *name* of *obj* by looking in *cls* and + only in *cls* (that is, it shouldn't look in the instance *__dict__* or + further along the MRO). This method should return the (computed) attribute + value or raise an :exc:`AttributeError` exception. + + The *owner* has the same meaning as the argument of the same name + for descriptor gettters (:meth:`object.__get__`) + .. method:: object.__setattr__(self, name, value) Called when an attribute assignment is attempted. This is called instead of diff --git a/Include/object.h b/Include/object.h --- a/Include/object.h +++ b/Include/object.h @@ -314,6 +314,7 @@ #endif typedef PyObject *(*getattrfunc)(PyObject *, char *); typedef PyObject *(*getattrofunc)(PyObject *, PyObject *); +typedef PyObject *(*getattrosuperfunc)(struct _typeobject*, PyObject *, PyObject *, PyObject* ); typedef int (*setattrfunc)(PyObject *, char *, PyObject *); typedef int (*setattrofunc)(PyObject *, PyObject *, PyObject *); typedef PyObject *(*reprfunc)(PyObject *); @@ -408,6 +409,9 @@ /* Type attribute cache version tag. Added in version 2.6 */ unsigned int tp_version_tag; + /* Look for attribute at one specific level */ + getattrosuperfunc tp_getattro_super; + #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ Py_ssize_t tp_allocs; @@ -519,6 +523,7 @@ PyAPI_FUNC(PyObject *) _PyObject_NextNotImplemented(PyObject *); #endif PyAPI_FUNC(PyObject *) PyObject_GenericGetAttr(PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) PyObject_GenericGetAttrSuper(PyTypeObject *, PyObject *, PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_GenericSetAttr(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_GenericSetDict(PyObject *, PyObject *, void *); @@ -819,7 +824,7 @@ PyObject *_py_xincref_tmp = (PyObject *)(op); \ if (_py_xincref_tmp != NULL) \ Py_INCREF(_py_xincref_tmp); \ - } while (0) + } while (0) #define Py_XDECREF(op) \ do { \ diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -173,9 +173,111 @@ self.assertRaises(TypeError, X.meth, c) +class SA (object): + def m(self): + return 'SA' + +class SB (SA): + def __getattribute__(self, nm): + dct = super().__getattribute__('__dict__') + if nm == 'm': + return lambda: 'SB' + + return super().__getattribute__(nm) + +class SBB (SB): + def __getattribute__(self, nm): + if nm == 'm': + return object.__getattribute__(self, nm) + return super().__getattribute__(nm) + + def m(self): + return 'SBB' + +class SC (SA): + def __getattribute__(self, nm): + dct = super().__getattribute__('__dict__') + if nm in dct: + return super().__getattribute__(nm) + + elif nm == 'm': + return lambda: 'SC' + + return super().__getattribute__(nm) + + def __getattribute_super__(cls, nm, self, owner): + print("SC", cls, nm, self, owner) + if nm == 'm': + return lambda: 'SCs' + + raise AttributeError(nm) + +class SCC (SC): + def __getattribute__(self, nm): + if nm == 'm': + return object.__getattribute__(self, nm) + return super().__getattribute__(nm) + + def m(self): + return 'SCC' + +class SD (SC): + def __getattribute_super__(cls, nm, self, owner): + print("SD", cls, nm, self, owner) + if nm == 'm': + return lambda: 'SDs' + + raise AttributeError(nm) + +class SE (SD): + def __getattribute_super__(cls, nm, self, owner): + print("SE", cls, nm, self, owner) + if nm == 'm': + return lambda: 'SEs' + + raise AttributeError(nm) + +class SF (SE): + pass + +class TestSuperHooks (unittest.TestCase): + def test_without_hook(self): + o = SBB() + self.assertEqual(o.m(), 'SBB') + self.assertEqual(super(SBB, o).m(), 'SA') + self.assertIs(SBB.mro()[1], SB) + o = SB() + self.assertEqual(o.m(), 'SB') + + def test_with_hook(self): + o = SCC() + self.assertEqual(o.m(), 'SCC') + self.assertEqual(super(SCC, o).m(), 'SCs') + self.assertIs(SCC.mro()[1], SC) + o = SC() + self.assertEqual(o.m(), 'SC') + + def test_class_level(self): + m = super(SBB, SBB).m + self.assertEqual(m, SA.m) + self.assertNotEqual(m, SBB.m) + + m = super(SCC, SCC).m + self.assertNotEqual(m, SA.m) + self.assertNotEqual(m, SCC.m) + self.assertEqual(m(), 'SCs') + + def test_levels(self): + o = SF() + + self.assertEqual(super(SF, o).m(), 'SEs') + self.assertEqual(super(SE, o).m(), 'SDs') + self.assertEqual(super(SD, o).m(), 'SCs') + + + def test_main(): - support.run_unittest(TestSuper) - + support.run_unittest(TestSuper, TestSuperHooks) if __name__ == "__main__": unittest.main() diff --git a/Objects/object.c b/Objects/object.c --- a/Objects/object.c +++ b/Objects/object.c @@ -1058,8 +1058,43 @@ return NULL; } +/* Generic implementation for tp_getattro_super */ + +PyObject* +PyObject_GenericGetAttrSuper( + PyTypeObject* tp, PyObject* name, PyObject* obj, PyObject* owner) +{ + PyObject* dict = tp->tp_dict; + PyObject* res; + PyObject* tmp; + + res = PyDict_GetItem(dict, name); + if (res != NULL) { + descrgetfunc f; + + Py_INCREF(res); + f = Py_TYPE(res)->tp_descr_get; + if (f != NULL) { + tmp = f(res, + /* Only pass 'obj' param if + this is instance-mode super + (See SF ID #743627) + */ + obj == owner ? (PyObject*)NULL : obj, + owner); + + Py_DECREF(res); + res = tmp; + } + return res; + } + + PyErr_SetObject(PyExc_AttributeError, name); + return NULL; +} /* Generic GetAttr functions - put these in your tp_[gs]etattro slot */ + PyObject * _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -44,6 +44,7 @@ _Py_IDENTIFIER(__doc__); _Py_IDENTIFIER(__getitem__); _Py_IDENTIFIER(__getattribute__); +_Py_IDENTIFIER(__getattribute_super__); _Py_IDENTIFIER(__hash__); _Py_IDENTIFIER(__module__); _Py_IDENTIFIER(__name__); @@ -2309,6 +2310,17 @@ Py_DECREF(tmp); } + /* And similarly for __getattribute_super__ */ + tmp = _PyDict_GetItemId(dict, &PyId___getattribute_super__); + if (tmp != NULL && PyFunction_Check(tmp)) { + tmp = PyStaticMethod_New(tmp); + if (tmp == NULL) + goto error; + if (_PyDict_SetItemId(dict, &PyId___getattribute_super__, tmp) < 0) + goto error; + Py_DECREF(tmp); + } + /* Add descriptors for custom slots from __slots__, or for __dict__ */ mp = PyHeapType_GET_MEMBERS(et); slotoffset = base->tp_basicsize; @@ -2411,7 +2423,7 @@ char *s; char *res_start = (char*)res; PyType_Slot *slot; - + /* Set the type name and qualname */ s = strrchr(spec->name, '.'); if (s == NULL) @@ -2432,7 +2444,7 @@ type->tp_name = spec->name; if (!type->tp_name) goto fail; - + /* Adjust for empty tuple bases */ if (!bases) { base = &PyBaseObject_Type; @@ -2516,7 +2528,7 @@ /* Set type.__module__ */ s = strrchr(spec->name, '.'); if (s != NULL) - _PyDict_SetItemId(type->tp_dict, &PyId___module__, + _PyDict_SetItemId(type->tp_dict, &PyId___module__, PyUnicode_FromStringAndSize( spec->name, (Py_ssize_t)(s - spec->name))); @@ -3801,6 +3813,15 @@ PyType_GenericAlloc, /* tp_alloc */ object_new, /* tp_new */ PyObject_Del, /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ + 0, /* tp_del */ + 0, /* tp_version_tag */ + PyObject_GenericGetAttrSuper /* tp_getattro_super */ }; @@ -4069,6 +4090,7 @@ type->tp_setattr = base->tp_setattr; type->tp_setattro = base->tp_setattro; } + COPYSLOT(tp_getattro_super); /* tp_reserved is ignored */ COPYSLOT(tp_repr); /* tp_hash see tp_richcompare */ @@ -4281,6 +4303,8 @@ type->tp_as_mapping = base->tp_as_mapping; if (type->tp_as_buffer == NULL) type->tp_as_buffer = base->tp_as_buffer; + if (type->tp_getattro_super == NULL) + type->tp_getattro_super = base->tp_getattro_super; } /* Link into each base class's list of subclasses */ @@ -4636,6 +4660,26 @@ return Py_None; } +static PyObject * +wrap_getattro_super(PyObject* self, PyObject* args, void* wrapped) +{ + PyObject *tp; + PyObject *name; + PyObject *object; + PyObject *owner; + + getattrosuperfunc func = (getattrosuperfunc)wrapped; + if (!PyArg_UnpackTuple(args, "", 4, 4, &tp, &name, &object, &owner)) + return NULL; + + if (!PyType_Check(tp)) { + PyErr_SetString(PyExc_TypeError, "first argument is not a type"); + return NULL; + } + + return (*func)(tp, name, object, owner); +} + /* Helper to check for object.__setattr__ or __delattr__ applied to a type. This is called the Carlo Verre hack after its discoverer. */ static int @@ -4656,6 +4700,7 @@ return 1; } + static PyObject * wrap_setattr(PyObject *self, PyObject *args, void *wrapped) { @@ -5376,6 +5421,35 @@ return call_method(self, &PyId___getattribute__, "(O)", name); } +static PyObject* +slot_tp_getattro_super(PyTypeObject* tp, + PyObject* name, PyObject* obj, PyObject* owner) +{ + PyObject* m; + PyObject* result; + PyObject* func; + descrgetfunc f; + m = _PyDict_GetItemId(tp->tp_dict, &PyId___getattribute_super__); + if (m == NULL) { + PyErr_SetString(PyExc_AttributeError, "__getattribute_super__"); + return NULL; + } + f = Py_TYPE(m)->tp_descr_get; + if (f != NULL) { + func = f(m, NULL, tp); + if (func == NULL) { + return NULL; + } + } else { + Py_INCREF(m); + func = m; + } + result = PyObject_CallFunctionObjArgs( + func, (PyObject*)tp, name, obj, owner, NULL); + Py_DECREF(func); + return result; +} + static PyObject * call_attribute(PyObject *self, PyObject *attr, PyObject *name) { @@ -5779,7 +5853,8 @@ PyWrapperFlag_KEYWORDS), TPSLOT("__new__", tp_new, slot_tp_new, NULL, ""), TPSLOT("__del__", tp_del, slot_tp_del, NULL, ""), - + TPSLOT("__getattribute_super__", tp_getattro_super, + slot_tp_getattro_super, wrap_getattro_super, "x.__getattribute_super(tp, name) ~> x.name"), BINSLOT("__add__", nb_add, slot_nb_add, "+"), RBINSLOT("__radd__", nb_add, slot_nb_add, @@ -6334,9 +6409,8 @@ } if (!skip) { - PyObject *mro, *res, *tmp, *dict; + PyObject *mro, *res, *tmp; PyTypeObject *starttype; - descrgetfunc f; Py_ssize_t i, n; starttype = su->obj_type; @@ -6355,35 +6429,34 @@ i++; res = NULL; /* keep a strong reference to mro because starttype->tp_mro can be - replaced during PyDict_GetItem(dict, name) */ + replaced during calls to tp_getattro_super */ Py_INCREF(mro); for (; i < n; i++) { + getattrosuperfunc ga; + tmp = PyTuple_GET_ITEM(mro, i); - if (PyType_Check(tmp)) - dict = ((PyTypeObject *)tmp)->tp_dict; - else - continue; - res = PyDict_GetItem(dict, name); - if (res != NULL) { - Py_INCREF(res); - f = Py_TYPE(res)->tp_descr_get; - if (f != NULL) { - tmp = f(res, - /* Only pass 'obj' param if - this is instance-mode super - (See SF ID #743627) - */ - (su->obj == (PyObject *) - su->obj_type - ? (PyObject *)NULL - : su->obj), - (PyObject *)starttype); - Py_DECREF(res); - res = tmp; - } + + if (!PyType_Check(tmp)) + continue; + + ga = ((PyTypeObject*)tmp)->tp_getattro_super; + if (!ga) { + /* XXX: Is this needed at all? */ + ga = PyObject_GenericGetAttrSuper; + } + + res = ga((PyTypeObject*)tmp, name, su->obj, su->obj_type); + if (res == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + continue; + } Py_DECREF(mro); - return res; - } + return NULL; + } + + Py_DECREF(mro); + return res; } Py_DECREF(mro); }