]> granicus.if.org Git - python/commitdiff
backport r67246 from the trunk
authorBenjamin Peterson <benjamin@python.org>
Mon, 17 Nov 2008 23:35:24 +0000 (23:35 +0000)
committerBenjamin Peterson <benjamin@python.org>
Mon, 17 Nov 2008 23:35:24 +0000 (23:35 +0000)
Lib/test/test_descr.py
Misc/NEWS
Objects/typeobject.c

index 3c29017ea49a73faa5e8f3b291c4fd3cf0706ecb..529939a2e8154bf67d172eab2fd2314344c2a83c 100644 (file)
@@ -1073,20 +1073,21 @@ def consistency_with_epg():
           (EditableScrollablePane, ScrollablePane, EditablePane,
            Pane, ScrollingMixin, EditingMixin, object))
 
+def raises(exc, expected, callable, *args):
+    try:
+        callable(*args)
+    except exc, msg:
+        if not str(msg).startswith(expected):
+            raise TestFailed, "Message %r, expected %r" % (str(msg),
+                                                           expected)
+    else:
+        raise TestFailed, "Expected %s" % exc
+
 mro_err_msg = """Cannot create a consistent method resolution
 order (MRO) for bases """
 
 def mro_disagreement():
     if verbose: print "Testing error messages for MRO disagreement..."
-    def raises(exc, expected, callable, *args):
-        try:
-            callable(*args)
-        except exc, msg:
-            if not str(msg).startswith(expected):
-                raise TestFailed, "Message %r, expected %r" % (str(msg),
-                                                               expected)
-        else:
-            raise TestFailed, "Expected %s" % exc
     class A(object): pass
     class B(A): pass
     class C(object): pass
@@ -4171,6 +4172,45 @@ def methodwrapper():
     vereq(t.__add__, (7,).__add__)
     vereq(hash(t.__add__), hash((7,).__add__))
 
+def test_getattr_hooks():
+    # issue 4230
+    class Descriptor(object):
+        counter = 0
+        def __get__(self, obj, objtype=None):
+            def getter(name):
+                self.counter += 1
+                raise AttributeError(name)
+            return getter
+
+    descr = Descriptor()
+    class A(object):
+        __getattribute__ = descr
+    class B(object):
+        __getattr__ = descr
+    class C(object):
+        __getattribute__ = descr
+        __getattr__ = descr
+
+    raises(AttributeError, "attr", getattr, A(), "attr")
+    vereq(descr.counter, 1)
+    raises(AttributeError, "attr", getattr, B(), "attr")
+    vereq(descr.counter, 2)
+    raises(AttributeError, "attr", getattr, C(), "attr")
+    vereq(descr.counter, 4)
+
+    import gc
+    class EvilGetattribute(object):
+        # This used to segfault
+        def __getattr__(self, name):
+            raise AttributeError(name)
+        def __getattribute__(self, name):
+            del EvilGetattribute.__getattr__
+            for i in range(5):
+                gc.collect()
+            raise AttributeError(name)
+
+    raises(AttributeError, "attr", getattr, EvilGetattribute(), "attr")
+
 def notimplemented():
     # all binary methods should be able to return a NotImplemented
     if verbose:
@@ -4352,6 +4392,7 @@ def test_main():
     methodwrapper()
     notimplemented()
     test_assign_slice()
+    test_getattr_hooks()
 
     if verbose: print "All OK"
 
index 4c45e4fc50ef3e9d5d69f0da967cf9a26a9fbb0c..25bffa21e59e90e67e617922c0fb83a595ba95ee 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,8 @@ What's New in Python 2.5.3?
 Core and builtins
 -----------------
 
+- Issue #4230: If ``__getattr__`` is a descriptor, it now functions correctly.
+
 - Issue #4048: The parser module now correctly validates relative imports.
 
 - Issue #4176: Fixed a crash when pickling an object which ``__reduce__``
index fd33fa47b7628c6e4dd35532c9a82ee41469d188..4c032e77e459b1061ebcb0b29544602ab916e9e4 100644 (file)
@@ -4737,6 +4737,24 @@ slot_tp_getattro(PyObject *self, PyObject *name)
                           "(O)", name);
 }
 
+static PyObject *
+call_attribute(PyObject *self, PyObject *attr, PyObject *name)
+{
+       PyObject *res, *descr = NULL;
+       descrgetfunc f = attr->ob_type->tp_descr_get;
+
+       if (f != NULL) {
+               descr = f(attr, self, (PyObject *)(self->ob_type));
+               if (descr == NULL)
+                       return NULL;
+               else
+                       attr = descr;
+       }
+       res = PyObject_CallFunctionObjArgs(attr, name, NULL);
+       Py_XDECREF(descr);
+       return res;
+}
+
 static PyObject *
 slot_tp_getattr_hook(PyObject *self, PyObject *name)
 {
@@ -4756,24 +4774,39 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name)
                if (getattribute_str == NULL)
                        return NULL;
        }
+       /* 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_Lookup(tp, getattr_str);
        if (getattr == NULL) {
                /* No __getattr__ hook: use a simpler dispatcher */
                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__
+          method. So we use _PyType_Lookup and create the method only when
+          needed, with call_attribute. */
        getattribute = _PyType_Lookup(tp, getattribute_str);
        if (getattribute == NULL ||
            (getattribute->ob_type == &PyWrapperDescr_Type &&
             ((PyWrapperDescrObject *)getattribute)->d_wrapped ==
             (void *)PyObject_GenericGetAttr))
                res = PyObject_GenericGetAttr(self, name);
-       else
-               res = PyObject_CallFunctionObjArgs(getattribute, self, name, NULL);
+       else {
+               Py_INCREF(getattribute);
+               res = call_attribute(self, getattribute, name);
+               Py_DECREF(getattribute);
+       }
        if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
                PyErr_Clear();
-               res = PyObject_CallFunctionObjArgs(getattr, self, name, NULL);
+               res = call_attribute(self, getattr, name);
        }
+       Py_DECREF(getattr);
        return res;
 }