diff -r 0f6c518183ef Lib/test/test_descr.py --- a/Lib/test/test_descr.py Wed Mar 26 02:00:54 2014 -0700 +++ b/Lib/test/test_descr.py Thu Mar 27 22:22:59 2014 -0700 @@ -1839,20 +1839,62 @@ order (MRO) for bases """ runner(X()) self.assertEqual(record, [1], name) class X(Checker): pass for attr, obj in env.items(): setattr(X, attr, obj) setattr(X, name, ErrDescr()) self.assertRaises(MyException, runner, X()) + def test_attributeerror_in_broken_descriptor(self): + # Issue1615: if a descriptor raises an AttributeError and the class + # defines __getattr__ the final AttributeError claims the descriptor + # doesn't exist (not good) + class Broken: + def __getattr__(self, name): + raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, name)) + @property + def problem(self): + return self.not_here + b = Broken() + self.assertRaisesRegex(AttributeError, "'Broken' object has no attribute 'not_here'", + getattr, b, 'problem') + + def test_attributeerror_in_good_descriptor(self): + # Issue1615: if a descriptor raises an AttributeError and the class + # defines __getattr__ the final AttributeError claims the descriptor + # doesn't exist (not good) + class Foolin: + def __getattr__(self, name): + raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, name)) + @property + def problem(self): + raise AttributeError("'Foolin' object is just fooling with ya!") + f = Foolin() + self.assertRaisesRegex(AttributeError, "'Foolin' object is just fooling with ya!", + getattr, f, 'problem') + + def test_attributeerror_in_stealth_descriptor(self): + # Issue1615: if a descriptor raises an AttributeError and the class + # defines __getattr__ the final AttributeError claims the descriptor + # doesn't exist (good in this case) + class Stealth: + def __getattr__(self, name): + raise AttributeError('%r object has no attribute %r' % (self.__class__.__name__, name)) + @property + def hidden(self): + raise AttributeError("I'm not here, I'm hidden!") + s = Stealth() + self.assertRaisesRegex(AttributeError, "I'm not here, I'm hidden!", + getattr, s, 'hidden') + def test_specials(self): # Testing special operators... # Test operators like __hash__ for which a built-in default exists # Test the default behavior for static classes class C(object): def __getitem__(self, i): if 0 <= i < 10: return i raise IndexError c1 = C() diff -r 0f6c518183ef Objects/typeobject.c --- a/Objects/typeobject.c Wed Mar 26 02:00:54 2014 -0700 +++ b/Objects/typeobject.c Thu Mar 27 22:22:59 2014 -0700 @@ -5844,20 +5844,21 @@ call_attribute(PyObject *self, PyObject res = PyObject_CallFunctionObjArgs(attr, name, NULL); Py_XDECREF(descr); return res; } static PyObject * slot_tp_getattr_hook(PyObject *self, PyObject *name) { PyTypeObject *tp = Py_TYPE(self); PyObject *getattr, *getattribute, *res; + PyObject *error_type, *error_value, *error_traceback; _Py_IDENTIFIER(__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 the attribute is present. So we use _PyType_Lookup and create the method only when needed, with call_attribute. */ getattr = _PyType_LookupId(tp, &PyId___getattr__); if (getattr == NULL) { /* No __getattr__ hook: use a simpler dispatcher */ @@ -5874,23 +5875,29 @@ slot_tp_getattr_hook(PyObject *self, PyO if (getattribute == NULL || (Py_TYPE(getattribute) == &PyWrapperDescr_Type && ((PyWrapperDescrObject *)getattribute)->d_wrapped == (void *)PyObject_GenericGetAttr)) res = PyObject_GenericGetAttr(self, name); else { Py_INCREF(getattribute); res = call_attribute(self, getattribute, name); Py_DECREF(getattribute); } + /* if an AttributeError is set, save it and call getattr; if getattr + sets an AttributeError, discard it and return the original + AttributeError */ if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Fetch(&error_type, &error_value, &error_traceback); PyErr_Clear(); res = call_attribute(self, getattr, name); + if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) + PyErr_Restore(error_type, error_value, error_traceback); } Py_DECREF(getattr); return res; } static int slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value) { PyObject *res; _Py_IDENTIFIER(__delattr__);