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,11 @@ result of implicit invocation via language syntax or built-in functions. See :ref:`special-lookup`. +.. method:: type.__locallookup__(cls, name) + + Optional method on the metaclass of a type that is called during attribute resolution + for objects (:meth:`object.__getattribute`) and :func:`super` objects. When the method + is not present attribute lookup looks in the class ``__dict__`` instead. .. 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,354 @@ +""" +Test suite for PEP 447: the __locallookup__ method on meta classes +""" +import unittest +import _testcapi + +from test import support + +# +# Type/object integration +# + +class Base1: + def m(self): + return 42 + + @property + def prop(self): + return 1 + + +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" + + @property + def PROP(self): + return 2 + + +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 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) + self.assertEqual(o.prop, 1) + + o = Sub1() + self.assertEqual(o.m(), 42) + + o = Base2() + self.assertEqual(o.m(), "TwentyOne") + self.assertEqual(o.prop, 2) + + 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): + 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] + + 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) + + + def test_meta_updates(self): + class ChangingMeta(type): + pass + + class WithChangingMeta(metaclass=ChangingMeta): + ATTR = [0] + + def m(self): + return "base" + + def M(self): + return "local!" + + o = WithChangingMeta() + self.assertEqual(o.m(), "base") + self.assertEqual(o.ATTR, [0]) + + def locallookup(cls, name): + try: + v = cls.__dict__[name.upper()] + if isinstance(v, list): + ret = v[0] + v[0] += 1 + return ret + return v + + except KeyError: + raise AttributeError(name) + + ChangingMeta.__locallookup__ = locallookup + + self.assertEqual(o.m(), "local!") + self.assertEqual(o.ATTR, 0) + self.assertEqual(o.ATTR, 1) + + +class WithCMeta(metaclass=_testcapi.TypeWithLocalLookup): + def m(self): + return 1 + + def M(self): + return 2 + + +class BaseForCMeta: + def m(self): + return 1 + + def M(self): + return 2 + + +class SubWithCMeta(BaseForCMeta, metaclass=_testcapi.TypeWithLocalLookup): + def m(self): + return 3 + + def M(self): + return 4 + + +class SubclassCMeta (_testcapi.TypeWithLocalLookup): + def __locallookup__(cls, name): + if name == 'x': + return cls.__dict__['y'] + return super().__locallookup__(name) + + +class WithSubclassCMeta (metaclass=SubclassCMeta): + def m(self): + return 'm' + + def M(self): + return 'M' + + def x(self): + return 'x' + + def y(self): + return 'y' + + +class Pep447TestC(unittest.TestCase): + def test_interface(self): + self.assertEqual( + WithCMeta.__dict__['M'], + _testcapi.TypeWithLocalLookup.__locallookup__(WithCMeta, 'm') + ) + + def test_basic(self): + o = WithCMeta() + self.assertEqual(o.m(), 2) + + o = SubWithCMeta() + self.assertEqual(o.m(), 4) + + self.assertEqual(super(SubWithCMeta, o).m(), 1) + + def test_subclassing(self): + o = WithSubclassCMeta() + self.assertEqual(o.m(), 'M') + self.assertEqual(o.x(), 'y') + + +# +# Super() integration +# + +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 Pep447TestSuper (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(Pep447TestPy, Pep447TestC, Pep447TestSuper) + +if __name__ == '__main__': + test_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/Modules/_testcapimodule.c b/Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2937,6 +2937,80 @@ + +/* + * Helper (meta) type for the pep447 tests + */ + +PyObject* lookup_locallookup(PyTypeObject* cls, PyObject* name) +{ + PyObject* result; + PyObject* upper_name = PyObject_CallMethod(name, "upper", ""); + if (upper_name == NULL) { + return NULL; + } + + result = PyDict_GetItem(cls->tp_dict, upper_name); + Py_DECREF(upper_name); + Py_XINCREF(result); + return result; +} + + +static PyTypeObject TypeWithLocalLookup = { + PyVarObject_HEAD_INIT(NULL, 0) + "TypeWithLocalLookup", + 0, /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + PyObject_GenericSetAttr, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE, /* tp_flags */ + "Type subclass with __locallookup__", + 0, /* traverseproc tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ + 0, /* 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 */ + lookup_locallookup, /* tp_locallookup */ +}; + + + static struct PyModuleDef _testcapimodule = { PyModuleDef_HEAD_INIT, "_testcapi", @@ -2991,6 +3065,12 @@ Py_INCREF(&PyInstanceMethod_Type); PyModule_AddObject(m, "instancemethod", (PyObject *)&PyInstanceMethod_Type); + TypeWithLocalLookup.tp_base = &PyType_Type; + TypeWithLocalLookup.tp_new = PyType_Type.tp_new; + TypeWithLocalLookup.tp_dealloc = PyType_Type.tp_dealloc; + PyType_Ready(&TypeWithLocalLookup); + PyModule_AddObject(m, "TypeWithLocalLookup", (PyObject*)&TypeWithLocalLookup); + TestError = PyErr_NewException("_testcapi.error", NULL, NULL); Py_INCREF(TestError); PyModule_AddObject(m, "error", TestError); 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 @@ -1100,8 +1100,7 @@ goto done; } - descr = _PyType_Lookup(tp, name); - Py_XINCREF(descr); + descr = _PyType_LookupName(tp, name); f = NULL; if (descr != NULL) { @@ -1192,8 +1191,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 @@ -1199,7 +1199,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. @@ -1214,10 +1214,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; } @@ -1809,16 +1810,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); } @@ -1832,6 +1838,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); @@ -1839,10 +1847,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); @@ -2550,21 +2561,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 */ @@ -2585,15 +2600,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 */ @@ -2611,11 +2639,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) { @@ -2640,7 +2668,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; @@ -2650,15 +2678,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; @@ -2668,11 +2697,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; } @@ -3015,7 +3045,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 */ }; @@ -4130,6 +4160,7 @@ * obvious to be done -- the type is on its own. */ } + COPYSLOT(tp_locallookup); } static int add_operators(PyTypeObject *); @@ -4832,6 +4863,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; @@ -5054,13 +5110,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) { @@ -5270,6 +5326,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; @@ -5416,7 +5485,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__ @@ -5429,7 +5497,6 @@ (void *)PyObject_GenericGetAttr)) res = PyObject_GenericGetAttr(self, name); else { - Py_INCREF(getattribute); res = call_attribute(self, getattribute, name); Py_DECREF(getattribute); } @@ -5529,6 +5596,7 @@ { PyTypeObject *tp = Py_TYPE(self); PyObject *get; + PyObject *result; _Py_IDENTIFIER(__get__); get = _PyType_LookupId(tp, &PyId___get__); @@ -5543,7 +5611,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 @@ -5783,7 +5853,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, @@ -6002,7 +6074,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; @@ -6058,6 +6130,7 @@ use_generic = 1; generic = p->function; } + Py_DECREF(descr); } while ((++p)->offset == offset); if (specific && !use_generic) *ptr = specific; @@ -6338,7 +6411,7 @@ } if (!skip) { - PyObject *mro, *res, *tmp, *dict; + PyObject *mro, *res, *tmp; PyTypeObject *starttype; descrgetfunc f; Py_ssize_t i, n; @@ -6359,17 +6432,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,