This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eric.snow
Recipients BreamoreBoy, Micah.Friesen, benjamin.peterson, daniel.urban, eric.araujo, eric.snow, gangesmaster, pitrou, rhettinger, thomaslee
Date 2013-02-16.04:20:21
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1360988422.56.0.0397033706379.issue1615@psf.upfronthosting.co.za>
In-reply-to
Content
Got bit by a variation of this today in 2.7:


class Spam(object):
    def __getattr__(self, attr):
        raise AttributeError(attr)
    @property
    def eggs(self):
        return self.ham

s = Spam()
s.eggs
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __getattr__
AttributeError: eggs


It took me a little while to figure out what was going on.  A real head-scratcher.  This is because the AttributeError was attributed to the property, but was actually caused by the __getattr__ call triggered by the property's code.  I would expect the AttributeError to reference "ham", not "eggs".  As already noted, if __getattr__() is not there, that's what happens.

Regardless, I'm just not seeing where the hurdle is to improving this behavior.  I certainly agree that this is not a feature.  It is the source of very mysterious failures.

I was surprised that AttributeError does not have an attribute to which the name would be bound.  If it did, then slot_tp_getattr_hook() could check against that:


    if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
        PyObject *tp, *exc, *tb, *exc_attr;

        PyErr_Fetch(&tp, &exc, &tb);
        exc_attr = PyObject_GetAttrString(exc, "attribute");
        PyErr_Restore(tp, exc, tb);

        if (!exc_attr || exc_attr == name) { 
            PyErr_Clear();
            res = call_attribute(self, getattr, name);
        }
        Py_XDECREF(exc_attr);
    }


Alternatively, when an AttributeError comes out of a getter in _PyObject_GenericSetAttrWithDict() (in either spot they're called), another exception (not AttributeError) could be raised with the original chained onto it.  Then slot_tp_getattr_hook() won't silently ignore it.  It would be something like this:


        if (f != NULL && PyDescr_IsData(descr)) {
            res = f(descr, obj, value);
            if (!res && PyErr_ExceptionMatches(PyExc_AttributeError)) {
                PyObject *msg = PyUnicode_FromFormat("getter failed for '%U'", name);
                /* implicit chaining here */
                PyErr_SetObject(PyExc_???Error, msg);
            }
            goto done;
        }


Conceptually, it's as though locating the attribute and extracting the value are lumped together here.  Distinguishing the two would help make this failure situation much less confusing.

Additionally, it would be really helpful to have a brief mention of this behavior (AttributeErrors in getters falling back to __getattr__) in the language reference entry for __getattr__ and/or descriptors.
History
Date User Action Args
2013-02-16 04:20:22eric.snowsetrecipients: + eric.snow, rhettinger, pitrou, gangesmaster, thomaslee, benjamin.peterson, eric.araujo, daniel.urban, BreamoreBoy, Micah.Friesen
2013-02-16 04:20:22eric.snowsetmessageid: <1360988422.56.0.0397033706379.issue1615@psf.upfronthosting.co.za>
2013-02-16 04:20:22eric.snowlinkissue1615 messages
2013-02-16 04:20:21eric.snowcreate