diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1392,6 +1392,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; @@ -481,7 +485,7 @@ PyAPI_FUNC(PyObject *) PyType_GenericNew(PyTypeObject *, PyObject *, PyObject *); #ifndef Py_LIMITED_API -PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *); +PyAPI_FUNC(PyObject *) _PyType_LookupName(PyTypeObject *, PyObject *); PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, _Py_Identifier *); PyAPI_FUNC(PyTypeObject *) _PyType_CalculateMetaclass(PyTypeObject *, PyObject *); #endif @@ -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_descr.py b/Lib/test/test_descr.py --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4657,7 +4657,7 @@ class MiscTests(unittest.TestCase): def test_type_lookup_mro_reference(self): - # Issue #14199: _PyType_Lookup() has to keep a strong reference to + # Issue #14199: _PyType_LookupName() has to keep a strong reference to # the type MRO because it may be modified during the lookup, if # __bases__ is set during the lookup for example. class MyKey(object): diff --git a/Lib/test/test_pep447.py b/Lib/test/test_pep447.py new file mode 100644 --- /dev/null +++ b/Lib/test/test_pep447.py @@ -0,0 +1,145 @@ +""" +Test suite for PEP 447: the __locallookup__ method on meta classes +""" +import unittest + +from test import support + +class Base1: + def m(self): + return 42 + +class Meta(type): + def __locallookup__(cls, name): + try: + return cls.__dict__[name.upper()] + except KeyError: + raise AttributeError(name) + +class Base2(metaclass=Meta): + def m(self): + return 21 + + def M(self): + return "TwentyOne" + + +class Sub1(Base1): + pass + +class Sub2(Base2): + pass + +class Sub12(Base1, Base2): + pass + +class Sub21(Base2, Base1): + pass + +class Sub12a(Base1, Base2): + def m(self): + return "Sub12a.m" + + def M(self): + return "Sub12a.M" + +class Sub21a(Base2, Base1): + def m(self): + return "Sub21a.m" + + def M(self): + return "Sub21a.M" + + +class DynamicAttr(type): + def __locallookup__(cls, name): + try: + value = cls.__dict__[name] + if isinstance(value, list): + # NOTE: Using a list to avoid modifying + # the class in a way that the attribute + # caching mechanism could see. + v = value[0] + value[0] = v + 1 + return lambda self, v=v: v + return value + + except KeyError: + raise AttributeError(name) + + except: + import traceback + traceback.print_exc() + raise + +class DynBase(metaclass=DynamicAttr): + attr = [10] + +class DynSub12(Base1, DynBase): + attr = [20] + +class DynSub21(DynBase, Base1): + attr = [30] + + +class Pep447TestPy(unittest.TestCase): + def test_no_default_implementation(self): + self.assertRaises(AttributeError, getattr, type, "__locallookup__") + + def test_trivial(self): + o = Base1() + self.assertEqual(o.m(), 42) + + o = Sub1() + self.assertEqual(o.m(), 42) + + o = Base2() + self.assertEqual(o.m(), "TwentyOne") + + o = Sub2() + self.assertEqual(o.m(), "TwentyOne") + + def test_multiple_inheritence(self): + o = Sub12() + self.assertEqual(Sub12.mro(), [Sub12, Base1, Base2, object]) + self.assertEqual(o.m(), 42) + + o = Sub21() + self.assertEqual(Sub21.mro(), [Sub21, Base2, Base1, object]) + self.assertEqual(o.m(), "TwentyOne") + + o = Sub12a() + self.assertEqual(Sub12a.mro(), [Sub12a, Base1, Base2, object]) + self.assertEqual(o.m(), "Sub12a.M") + + o = Sub21a() + self.assertEqual(Sub21a.mro(), [Sub21a, Base2, Base1, object]) + self.assertEqual(o.m(), "Sub21a.M") + + def test_attribute_cache_disabled(self): + o = DynBase() + self.assertEqual(o.attr(), 10) + self.assertEqual(o.attr(), 11) + + # Look: the method itself is constant: + m = o.attr + self.assertEqual(m(), 12) + self.assertEqual(m(), 12) + + o = DynSub12() + self.assertEqual(o.attr(), 20) + self.assertEqual(o.attr(), 21) + + o = DynSub21() + self.assertEqual(o.attr(), 30) + self.assertEqual(o.attr(), 31) + + +class Pep447TestC(unittest.TestCase): + pass + +def test_main(): + support.run_unittest(Pep447TestPy, Pep447TestC) + +if __name__ == '__main__': + test_main() 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,80 @@ c = f().__closure__[0] self.assertRaises(TypeError, X.meth, c) +class meta1 (type): + def __locallookup__(cls, name): + try: + return cls.__dict__[name + '_meta1'] + except KeyError: + raise AttributeError(name) + +class meta2 (meta1): + def __locallookup__(cls, name): + try: + return cls.__dict__[name + '_meta2'] + except KeyError: + raise AttributeError(name) + +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(super(SB, o).m(), "SA.m_meta1") + + def test_subclass(self): + o = SC() + 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(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,7 +1169,6 @@ self.assertEqual(ns, ns_roundtrip, pname) - def test_main(): run_unittest(TypesTests, MappingProxyTests, ClassCreationTests, SimpleNamespaceTests) diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -203,8 +203,7 @@ PyObject *self = fn->m_self; PyObject *name = PyUnicode_FromString(fn->m_ml->ml_name); if (name != NULL) { - PyObject *mo = _PyType_Lookup(Py_TYPE(self), name); - Py_XINCREF(mo); + PyObject *mo = _PyType_LookupName(Py_TYPE(self), name); Py_DECREF(name); if (mo != NULL) { PyObject *res = PyObject_Repr(mo); diff --git a/Objects/classobject.c b/Objects/classobject.c --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -117,15 +117,16 @@ if (PyType_Ready(tp) < 0) return NULL; } - descr = _PyType_Lookup(tp, name); + descr = _PyType_LookupName(tp, name); } if (descr != NULL) { descrgetfunc f = TP_DESCR_GET(descr->ob_type); - if (f != NULL) - return f(descr, obj, (PyObject *)obj->ob_type); - else { - Py_INCREF(descr); + if (f != NULL) { + PyObject* result = f(descr, obj, (PyObject *)obj->ob_type); + Py_DECREF(descr); + return result; + } else { return descr; } } @@ -473,14 +474,15 @@ if (PyType_Ready(tp) < 0) return NULL; } - descr = _PyType_Lookup(tp, name); + descr = _PyType_LookupName(tp, name); if (descr != NULL) { descrgetfunc f = TP_DESCR_GET(descr->ob_type); - if (f != NULL) - return f(descr, self, (PyObject *)self->ob_type); - else { - Py_INCREF(descr); + if (f != NULL) { + PyObject* result = f(descr, self, (PyObject *)self->ob_type); + Py_DECREF(descr); + return result; + } else { return descr; } } diff --git a/Objects/object.c b/Objects/object.c --- a/Objects/object.c +++ b/Objects/object.c @@ -1084,8 +1084,7 @@ goto done; } - descr = _PyType_Lookup(tp, name); - Py_XINCREF(descr); + descr = _PyType_LookupName(tp, name); f = NULL; if (descr != NULL) { @@ -1176,8 +1175,7 @@ Py_INCREF(name); - descr = _PyType_Lookup(tp, name); - Py_XINCREF(descr); + descr = _PyType_LookupName(tp, name); f = NULL; if (descr != NULL) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1190,7 +1190,7 @@ Two variants: - lookup_maybe() returns NULL without raising an exception - when the _PyType_Lookup() call fails; + when the _PyType_LookupName() call fails; - lookup_method() always raises an exception upon errors. @@ -1205,10 +1205,11 @@ res = _PyType_LookupId(Py_TYPE(self), attrid); if (res != NULL) { descrgetfunc f; - if ((f = Py_TYPE(res)->tp_descr_get) == NULL) - Py_INCREF(res); - else - res = f(res, self, (PyObject *)(Py_TYPE(self))); + if ((f = Py_TYPE(res)->tp_descr_get) != NULL) { + PyObject* tmp = f(res, self, (PyObject *)(Py_TYPE(self))); + Py_DECREF(res); + res = tmp; + } } return res; } @@ -1800,16 +1801,21 @@ if (base != NULL) { descrgetfunc func; PyObject *descr = get_dict_descriptor(base); + PyObject* result; + if (descr == NULL) { raise_dict_descr_error(obj); return NULL; } func = Py_TYPE(descr)->tp_descr_get; if (func == NULL) { + Py_DECREF(descr); raise_dict_descr_error(obj); return NULL; } - return func(descr, obj, (PyObject *)(Py_TYPE(obj))); + result = func(descr, obj, (PyObject *)(Py_TYPE(obj))); + Py_DECREF(descr); + return result; } return PyObject_GenericGetDict(obj, context); } @@ -1823,6 +1829,8 @@ base = get_builtin_base_with_dict(Py_TYPE(obj)); if (base != NULL) { descrsetfunc func; + int result; + PyObject *descr = get_dict_descriptor(base); if (descr == NULL) { raise_dict_descr_error(obj); @@ -1830,10 +1838,13 @@ } func = Py_TYPE(descr)->tp_descr_set; if (func == NULL) { + Py_DECREF(descr); raise_dict_descr_error(obj); return -1; } - return func(descr, obj, value); + result = func(descr, obj, value); + Py_DECREF(descr); + return result; } /* Almost like PyObject_GenericSetDict, but allow __dict__ to be deleted. */ dictptr = _PyObject_GetDictPtr(obj); @@ -2541,21 +2552,25 @@ /* Internal API to look for a name through the MRO. - This returns a borrowed reference, and doesn't set an exception! */ + This returns a new reference, and doesn't set an exception! */ PyObject * -_PyType_Lookup(PyTypeObject *type, PyObject *name) +_PyType_LookupName(PyTypeObject *type, PyObject *name) { Py_ssize_t i, n; PyObject *mro, *res, *base, *dict; unsigned int h; - if (MCACHE_CACHEABLE_NAME(name) && + if (PyType_HasFeature(type, Py_TPFLAGS_READY) && + !Py_TYPE(type)->tp_locallookup && MCACHE_CACHEABLE_NAME(name) && PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { /* fast path */ h = MCACHE_HASH_METHOD(type, name); if (method_cache[h].version == type->tp_version_tag && - method_cache[h].name == name) - return method_cache[h].value; + method_cache[h].name == name) { + res = method_cache[h].value; + Py_XINCREF(res); + return res; + } } /* Look in tp_dict of types in MRO */ @@ -2576,15 +2591,28 @@ for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); assert(PyType_Check(base)); - dict = ((PyTypeObject *)base)->tp_dict; - assert(dict && PyDict_Check(dict)); - res = PyDict_GetItem(dict, name); - if (res != NULL) - break; + if (Py_TYPE(base)->tp_locallookup) { + res = Py_TYPE(base)->tp_locallookup((PyTypeObject*)base, name); + if (res != NULL) { + break; + } + + } else { + dict = ((PyTypeObject *)base)->tp_dict; + assert(dict && PyDict_Check(dict)); + res = PyDict_GetItem(dict, name); + if (res != NULL) { + Py_INCREF(res); + break; + } + } } Py_DECREF(mro); - if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) { + if (PyType_HasFeature(type, Py_TPFLAGS_READY) && + !Py_TYPE(type)->tp_locallookup + && MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) { + h = MCACHE_HASH_METHOD(type, name); method_cache[h].version = type->tp_version_tag; method_cache[h].value = res; /* borrowed */ @@ -2602,11 +2630,11 @@ oname = _PyUnicode_FromId(name); /* borrowed */ if (oname == NULL) return NULL; - return _PyType_Lookup(type, oname); + return _PyType_LookupName(type, oname); } /* This is similar to PyObject_GenericGetAttr(), - but uses _PyType_Lookup() instead of just looking in type->tp_dict. */ + but uses _PyType_LookupName() instead of just looking in type->tp_dict. */ static PyObject * type_getattro(PyTypeObject *type, PyObject *name) { @@ -2631,7 +2659,7 @@ meta_get = NULL; /* Look for the attribute in the metatype */ - meta_attribute = _PyType_Lookup(metatype, name); + meta_attribute = _PyType_LookupName(metatype, name); if (meta_attribute != NULL) { meta_get = Py_TYPE(meta_attribute)->tp_descr_get; @@ -2641,15 +2669,16 @@ * writes. Assume the attribute is not overridden in * type's tp_dict (and bases): call the descriptor now. */ - return meta_get(meta_attribute, (PyObject *)type, + PyObject* result = meta_get(meta_attribute, (PyObject *)type, (PyObject *)metatype); + Py_DECREF(meta_attribute); + return result; } - Py_INCREF(meta_attribute); } /* No data descriptor found on metatype. Look in tp_dict of this * type and its bases */ - attribute = _PyType_Lookup(type, name); + attribute = _PyType_LookupName(type, name); if (attribute != NULL) { /* Implement descriptor functionality, if any */ descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get; @@ -2659,11 +2688,12 @@ if (local_get != NULL) { /* NULL 2nd argument indicates the descriptor was * found on the target object itself (or a base) */ - return local_get(attribute, (PyObject *)NULL, + PyObject* result = local_get(attribute, (PyObject *)NULL, (PyObject *)type); + Py_DECREF(attribute); + return result; } - Py_INCREF(attribute); return attribute; } @@ -3006,7 +3036,7 @@ 0, /* tp_alloc */ type_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ - (inquiry)type_is_gc, /* tp_is_gc */ + (inquiry)type_is_gc /* tp_is_gc */ }; @@ -4121,6 +4151,7 @@ * obvious to be done -- the type is on its own. */ } + COPYSLOT(tp_locallookup); } static int add_operators(PyTypeObject *); @@ -4819,6 +4850,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; @@ -5041,13 +5097,13 @@ func = _PyType_LookupId(Py_TYPE(self), &PyId___getitem__); if (func != NULL) { - if ((f = Py_TYPE(func)->tp_descr_get) == NULL) - Py_INCREF(func); - else { - func = f(func, self, (PyObject *)(Py_TYPE(self))); - if (func == NULL) { + if ((f = Py_TYPE(func)->tp_descr_get) != NULL) { + PyObject* tmp = f(func, self, (PyObject *)(Py_TYPE(self))); + Py_DECREF(func); + if (tmp == NULL) { return NULL; } + func = tmp; } ival = PyLong_FromSsize_t(i); if (ival != NULL) { @@ -5257,6 +5313,19 @@ SLOT1(slot_nb_inplace_true_divide, "__itruediv__", 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) { PyObject *func, *res; @@ -5403,7 +5472,6 @@ tp->tp_getattro = slot_tp_getattro; return slot_tp_getattro(self, name); } - Py_INCREF(getattr); /* speed hack: we could use lookup_maybe, but that would resolve the method fully for each attribute lookup for classes with __getattr__, even when self has the default __getattribute__ @@ -5416,7 +5484,6 @@ (void *)PyObject_GenericGetAttr)) res = PyObject_GenericGetAttr(self, name); else { - Py_INCREF(getattribute); res = call_attribute(self, getattribute, name); Py_DECREF(getattribute); } @@ -5516,6 +5583,7 @@ { PyTypeObject *tp = Py_TYPE(self); PyObject *get; + PyObject *result; _Py_IDENTIFIER(__get__); get = _PyType_LookupId(tp, &PyId___get__); @@ -5530,7 +5598,9 @@ obj = Py_None; if (type == NULL) type = Py_None; - return PyObject_CallFunctionObjArgs(get, self, obj, type, NULL); + result = PyObject_CallFunctionObjArgs(get, self, obj, type, NULL); + Py_DECREF(get); + return result; } static int @@ -5770,7 +5840,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, @@ -5989,7 +6061,7 @@ return p; } do { - descr = _PyType_Lookup(type, p->name_strobj); + descr = _PyType_LookupName(type, p->name_strobj); if (descr == NULL) { if (ptr == (void**)&type->tp_iternext) { specific = (void *)_PyObject_NextNotImplemented; @@ -6045,6 +6117,7 @@ use_generic = 1; generic = p->function; } + Py_DECREF(descr); } while ((++p)->offset == offset); if (specific && !use_generic) *ptr = specific; @@ -6325,7 +6398,7 @@ } if (!skip) { - PyObject *mro, *res, *tmp, *dict; + PyObject *mro, *res, *tmp; PyTypeObject *starttype; descrgetfunc f; Py_ssize_t i, n; @@ -6346,17 +6419,22 @@ 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; - else + if (PyType_Check(tmp)) { + if (Py_TYPE(tmp)->tp_locallookup) { + res = Py_TYPE(tmp)->tp_locallookup((PyTypeObject*)tmp, name); + } else { + res = PyDict_GetItem(((PyTypeObject*)tmp)->tp_dict, name); + Py_XINCREF(res); + } + } 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,