diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1381,6 +1381,15 @@ result of implicit invocation via language syntax or built-in functions. See :ref:`special-lookup`. +.. method:: type.__locallookup__(cls, name) + + Called unconditionally on the metaclass of a type on the MRO by the ``__getattribute__`` + method of :class:`super`. This method looks up *name* in the namespace of *cls* and + returns the value without invoking descriptors. Raises :exc:`AttributeError` when + the *name* cannot be found. + + The default implementation on :class:`type` looks up *name* in the ``__dict__`` of + *cls*. .. method:: object.__setattr__(self, name, value) diff --git a/Include/object.h b/Include/object.h --- a/Include/object.h +++ b/Include/object.h @@ -326,6 +326,7 @@ typedef int (*initproc)(PyObject *, PyObject *, PyObject *); typedef PyObject *(*newfunc)(struct _typeobject *, PyObject *, PyObject *); typedef PyObject *(*allocfunc)(struct _typeobject *, Py_ssize_t); +typedef PyObject* (*locallookupfunc)(struct _typeobject*, PyObject*); #ifdef Py_LIMITED_API typedef struct _typeobject PyTypeObject; /* opaque */ @@ -408,6 +409,9 @@ /* Type attribute cache version tag. Added in version 2.6 */ unsigned int tp_version_tag; + /* local lookups in tp_dict. Added in version 3.4 */ + locallookupfunc tp_locallookup; + #ifdef COUNT_ALLOCS /* these must be last and never explicitly initialized */ Py_ssize_t tp_allocs; @@ -819,7 +823,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 @@ -172,9 +172,79 @@ c = f().__closure__[0] self.assertRaises(TypeError, X.meth, c) +class meta1 (type): + def __locallookup__(cls, name): + v = cls.__dict__[name + '_meta1'] + return v + +class meta2 (meta1): + def __locallookup__(cls, name): + v = cls.__dict__[name + '_meta2'] + return v + +class SA (metaclass=meta1): + def m(self): + return "SA.m" + + def m_meta1(self): + return "SA.m_meta1" + + def m_meta2(self): + return "SA.m_meta1" + +class SB (SA): + def m(self): + return "SB.m" + + def m_meta1(self): + return "SB.m_meta1" + + def m_meta2(self): + return "SB.m_meta1" + +class SC (SB, metaclass=meta2): + def m(self): + return "SC.m" + + def m_meta1(self): + return "SC.m_meta1" + + def m_meta2(self): + return "SC.m_meta1" + +class SD (SC): + def m(self): + return "SD.m" + + def m_meta1(self): + return "SD.m_meta1" + + def m_meta2(self): + return "SD.m_meta1" + + +class TestSuperLocalLookup (unittest.TestCase): + def test_basic(self): + o = SB() + self.assertEqual(o.m(), "SB.m") + self.assertEqual(super(SB, o).m(), "SA.m_meta1") + + def test_subclass(self): + o = SC() + self.assertEqual(o.m(), "SC.m") + self.assertEqual(super(SC, o).m(), "SB.m_meta1") + self.assertEqual(super(SB, o).m(), "SA.m_meta1") + + def subclass_meta_update(self): + o = SD() + self.assertEqual(o.m(), "SD.m") + self.assertEqual(super(SD, o).m(), "SC.m_meta2") + self.assertEqual(super(SC, o).m(), "SB.m_meta1") + self.assertEqual(super(SB, o).m(), "SA.m_meta1") + def test_main(): - support.run_unittest(TestSuper) + support.run_unittest(TestSuper, TestSuperLocalLookup) if __name__ == "__main__": diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -864,11 +864,11 @@ check((1,2,3), vsize('') + 3*self.P) # type # static type: PyTypeObject - s = vsize('P2n15Pl4Pn9Pn11PI') + s = vsize('P2n15Pl4Pn9Pn11PIP') check(int, s) # (PyTypeObject + PyNumberMethods + PyMappingMethods + # PySequenceMethods + PyBufferProcs + 4P) - s = vsize('P2n15Pl4Pn9Pn11PI') + struct.calcsize('34P 3P 10P 2P 4P') + s = vsize('P2n15Pl4Pn9Pn11PIP') + struct.calcsize('34P 3P 10P 2P 4P') # Separate block for PyDictKeysObject with 4 entries s += struct.calcsize("2nPn") + 4*struct.calcsize("n2P") # class diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -1169,10 +1169,28 @@ self.assertEqual(ns, ns_roundtrip, pname) +class TypeLocalLookupTests (unittest.TestCase): + def test_called_on_class(self): + self.assertEqual(object.__locallookup__('__init__'), object.__init__) + self.assertRaises(AttributeError, object.__locallookup__, 'nosuchname') + + class MyClass: + def m(self): pass + + @property + def aproperty(self): + return 42 + + self.assertEqual(MyClass.__locallookup__('m'), MyClass.__dict__['m']) + self.assertEqual(MyClass.__locallookup__('aproperty'), MyClass.__dict__['aproperty']) + + def test_called_on_instance (unittest.TestCase): + self.assertRaises(AttributeError, object().__locallookup__('__init__') + def test_main(): run_unittest(TypesTests, MappingProxyTests, ClassCreationTests, - SimpleNamespaceTests) + SimpleNamespaceTests, TypeLocalLookupTests) if __name__ == '__main__': test_main() diff --git a/Objects/typeobject.c b/Objects/typeobject.c --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2961,6 +2961,20 @@ return type->tp_flags & Py_TPFLAGS_HEAPTYPE; } +static PyObject* +type_locallookup(PyTypeObject* type, PyObject* name) +{ + PyObject* result; + + if (!type->tp_dict) { + return NULL; + } + + result = PyDict_GetItem(type->tp_dict, name); + Py_XINCREF(result); + return result; +} + PyTypeObject PyType_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "type", /* tp_name */ @@ -3003,6 +3017,14 @@ type_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ (inquiry)type_is_gc, /* 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 */ + type_locallookup /* tp_locallookup */ }; @@ -4117,6 +4139,7 @@ * obvious to be done -- the type is on its own. */ } + COPYSLOT(tp_locallookup); } static int add_operators(PyTypeObject *); @@ -4815,6 +4838,31 @@ } static PyObject * +wrap_locallookup(PyObject* self, PyObject* args, void* wrapped) +{ + locallookupfunc func = (locallookupfunc)wrapped; + PyObject* name; + PyObject* result; + + if (!check_num_args(args, 1)) { + return NULL; + } + + if (!PyType_Check(self)) { + PyErr_SetString(PyExc_TypeError, "__locallookup__() called with non-type 'self'"); + return NULL; + } + + name = PyTuple_GET_ITEM(args, 0); + result = (*func)((PyTypeObject*)self, name); + + if (result == NULL && !PyErr_Occurred()) { + PyErr_SetObject(PyExc_AttributeError, name); + } + return result; +} + +static PyObject * wrap_init(PyObject *self, PyObject *args, void *wrapped, PyObject *kwds) { initproc func = (initproc)wrapped; @@ -5251,6 +5299,20 @@ SLOT1BIN(slot_nb_true_divide, nb_true_divide, "__truediv__", "__rtruediv__") SLOT1(slot_nb_inplace_floor_divide, "__ifloordiv__", PyObject *, "O") SLOT1(slot_nb_inplace_true_divide, "__itruediv__", PyObject *, "O") +//SLOT1(slot_tp_locallookup, "__locallookup__", PyObject *, "O") +// +static PyObject * +slot_tp_locallookup(PyTypeObject* self, PyObject* name) +{ + PyObject* result; + _Py_IDENTIFIER(__locallookup__); + + result = call_method((PyObject*)self, &PyId___locallookup__, "(O)", name); + if (result == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + } + return result; +} static PyObject * slot_tp_repr(PyObject *self) @@ -5766,7 +5828,9 @@ PyWrapperFlag_KEYWORDS), TPSLOT("__new__", tp_new, slot_tp_new, NULL, ""), TPSLOT("__del__", tp_del, slot_tp_del, NULL, ""), - + TPSLOT("__locallookup__", tp_locallookup, slot_tp_locallookup, wrap_locallookup, + "type.__locallookup__(name)" + ), BINSLOT("__add__", nb_add, slot_nb_add, "+"), RBINSLOT("__radd__", nb_add, slot_nb_add, @@ -6321,7 +6385,7 @@ } if (!skip) { - PyObject *mro, *res, *tmp, *dict; + PyObject *mro, *res, *tmp; PyTypeObject *starttype; descrgetfunc f; Py_ssize_t i, n; @@ -6342,17 +6406,16 @@ i++; res = NULL; /* keep a strong reference to mro because starttype->tp_mro can be - replaced during PyDict_GetItem(dict, name) */ + replaced during tp_locallookup(tmp, name) */ Py_INCREF(mro); for (; i < n; i++) { tmp = PyTuple_GET_ITEM(mro, i); if (PyType_Check(tmp)) - dict = ((PyTypeObject *)tmp)->tp_dict; + res = Py_TYPE(tmp)->tp_locallookup((PyTypeObject*)tmp, name); 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,