]> granicus.if.org Git - python/commitdiff
Merged revisions 67246 via svnmerge from
authorBenjamin Peterson <benjamin@python.org>
Mon, 17 Nov 2008 22:44:30 +0000 (22:44 +0000)
committerBenjamin Peterson <benjamin@python.org>
Mon, 17 Nov 2008 22:44:30 +0000 (22:44 +0000)
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r67246 | benjamin.peterson | 2008-11-17 16:39:09 -0600 (Mon, 17 Nov 2008) | 5 lines

  when __getattr__ is a descriptor, call it correctly; fixes #4230

  patch from Ziga Seilnacht
........

Lib/test/test_descr.py
Misc/NEWS
Objects/typeobject.c

index f74073811fef7a0c4f5dbdf41d1675482dfcb1b3..b57e0fae05b928375eab3fede8f977f58dedd977 100644 (file)
@@ -4289,6 +4289,46 @@ order (MRO) for bases """
         c[1:2] = 3
         self.assertEqual(c.value, 3)
 
+    def test_getattr_hooks(self):
+        # 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
+
+        self.assertRaises(AttributeError, getattr, A(), "attr")
+        self.assertEquals(descr.counter, 1)
+        self.assertRaises(AttributeError, getattr, B(), "attr")
+        self.assertEquals(descr.counter, 2)
+        self.assertRaises(AttributeError, getattr, C(), "attr")
+        self.assertEquals(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)
+
+        self.assertRaises(AttributeError, getattr, EvilGetattribute(), "attr")
+
 
 class DictProxyTests(unittest.TestCase):
     def setUp(self):
index b9db17bb904c3d4c86fba2a3fb889c4f3748817c..f9bc86c64ac3183b377a7774b367cb8dcfca8fd7 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,8 @@ What's New in Python 2.6.1 alpha 1
 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 #4225: ``from __future__ import unicode_literals`` didn't work in an
index 405b77376c5637989cc180ec110f3e39f38f9315..3f790e88242253db508660018ffdcd574d4c233a 100644 (file)
@@ -5390,6 +5390,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 = Py_TYPE(attr)->tp_descr_get;
+
+       if (f != NULL) {
+               descr = f(attr, self, (PyObject *)(Py_TYPE(self)));
+               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)
 {
@@ -5409,24 +5427,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 ||
            (Py_TYPE(getattribute) == &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;
 }