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,17 @@ See :ref:`special-lookup`. +.. method:: object.__getattribute_super__(self, cls, name) + + 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* 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. + + .. 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 *); 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_GenericGetAttrInClass(PyTypeObject*, 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/Objects/object.c b/Objects/object.c --- a/Objects/object.c +++ b/Objects/object.c @@ -1060,6 +1060,30 @@ /* Generic GetAttr functions - put these in your tp_[gs]etattro slot */ +PyObject* +PyObject_GenericGetAttrInClass(PyTypeObject* tp, PyObject* obj, PyObject* name) +{ + PyObject* descr; + descrgetfunc f; + + descr = PyDict_GetItem(tp->tp_dict, name); + if (descr == NULL) { + PyErr_Format(PyExc_AttributeError, + "'%.50s' object has no attribute '%U'", + tp->tp_name, name); + return NULL; + } + + f = descr->ob_type->tp_descr_get; + if (f != NULL) { + PyObject* res = f(descr, obj, (PyObject*)obj->ob_type); + Py_DECREF(descr); + return res; + } else { + return descr; + } +} + 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__); @@ -2411,7 +2412,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 +2433,7 @@ type->tp_name = spec->name; if (!type->tp_name) goto fail; - + /* Adjust for empty tuple bases */ if (!bases) { base = &PyBaseObject_Type; @@ -2516,7 +2517,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))); @@ -4069,6 +4070,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 +4283,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 +4640,24 @@ return Py_None; } +static PyObject * +wrap_getattro_super(PyObject* self, PyObject* args, void* wrapped) +{ + PyObject *tp; + PyObject *name; + + getattrosuperfunc func = (getattrosuperfunc)wrapped; + if (!PyArg_UnpackTuple(args, "", 2, 2, &tp, &name)) + return NULL; + + if (!PyType_Check(tp)) { + PyErr_SetString(PyExc_TypeError, "first argument is not a type"); + return NULL; + } + + return (*func)(tp, self, name); +} + /* 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 +4678,7 @@ return 1; } + static PyObject * wrap_setattr(PyObject *self, PyObject *args, void *wrapped) { @@ -5376,6 +5399,12 @@ return call_method(self, &PyId___getattribute__, "(O)", name); } +static PyObject* +slot_tp_getattro_super(PyTypeObject* tp, PyObject* self, PyObject* name) +{ + return call_method(self, &PyId___getattribute_super__, "(OO)", tp, name); +} + static PyObject * call_attribute(PyObject *self, PyObject *attr, PyObject *name) { @@ -5779,7 +5808,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, @@ -6359,10 +6389,26 @@ Py_INCREF(mro); for (; i < n; i++) { tmp = PyTuple_GET_ITEM(mro, i); - if (PyType_Check(tmp)) + if (PyType_Check(tmp)) { dict = ((PyTypeObject *)tmp)->tp_dict; - else + if (((PyTypeObject*)tmp)->tp_getattro_super != NULL) { + res = (((PyTypeObject*)tmp)->tp_getattro_super)((PyTypeObject*)tmp, (su->obj == (PyObject *)su->obj_type ? (PyObject *)NULL : su->obj), name); + if (res == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + continue; + } + Py_DECREF(mro); + return NULL; + } + + Py_DECREF(mro); + return res; + } + + } else continue; + res = PyDict_GetItem(dict, name); if (res != NULL) { Py_INCREF(res);