Skip to content

Commit 763069a

Browse files
author
benjamin.peterson
committed
when __getattr__ is a descriptor, call it correctly; fixes #4230
patch from Ziga Seilnacht git-svn-id: http://svn.python.org/projects/python/trunk@67246 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent cd9fbf3 commit 763069a

3 files changed

Lines changed: 78 additions & 3 deletions

File tree

Lib/test/test_descr.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4289,6 +4289,46 @@ def __setslice__(self, start, stop, value):
42894289
c[1:2] = 3
42904290
self.assertEqual(c.value, 3)
42914291

4292+
def test_getattr_hooks(self):
4293+
# issue 4230
4294+
4295+
class Descriptor(object):
4296+
counter = 0
4297+
def __get__(self, obj, objtype=None):
4298+
def getter(name):
4299+
self.counter += 1
4300+
raise AttributeError(name)
4301+
return getter
4302+
4303+
descr = Descriptor()
4304+
class A(object):
4305+
__getattribute__ = descr
4306+
class B(object):
4307+
__getattr__ = descr
4308+
class C(object):
4309+
__getattribute__ = descr
4310+
__getattr__ = descr
4311+
4312+
self.assertRaises(AttributeError, getattr, A(), "attr")
4313+
self.assertEquals(descr.counter, 1)
4314+
self.assertRaises(AttributeError, getattr, B(), "attr")
4315+
self.assertEquals(descr.counter, 2)
4316+
self.assertRaises(AttributeError, getattr, C(), "attr")
4317+
self.assertEquals(descr.counter, 4)
4318+
4319+
import gc
4320+
class EvilGetattribute(object):
4321+
# This used to segfault
4322+
def __getattr__(self, name):
4323+
raise AttributeError(name)
4324+
def __getattribute__(self, name):
4325+
del EvilGetattribute.__getattr__
4326+
for i in range(5):
4327+
gc.collect()
4328+
raise AttributeError(name)
4329+
4330+
self.assertRaises(AttributeError, getattr, EvilGetattribute(), "attr")
4331+
42924332

42934333
class DictProxyTests(unittest.TestCase):
42944334
def setUp(self):

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ What's New in Python 2.7 alpha 1
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #4230: If ``__getattr__`` is a descriptor, it now functions correctly.
16+
1517
- Issue #4048: The parser module now correctly validates relative imports.
1618

1719
- Issue #4225: ``from __future__ import unicode_literals`` didn't work in an

Objects/typeobject.c

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5390,6 +5390,24 @@ slot_tp_getattro(PyObject *self, PyObject *name)
53905390
"(O)", name);
53915391
}
53925392

5393+
static PyObject *
5394+
call_attribute(PyObject *self, PyObject *attr, PyObject *name)
5395+
{
5396+
PyObject *res, *descr = NULL;
5397+
descrgetfunc f = Py_TYPE(attr)->tp_descr_get;
5398+
5399+
if (f != NULL) {
5400+
descr = f(attr, self, (PyObject *)(Py_TYPE(self)));
5401+
if (descr == NULL)
5402+
return NULL;
5403+
else
5404+
attr = descr;
5405+
}
5406+
res = PyObject_CallFunctionObjArgs(attr, name, NULL);
5407+
Py_XDECREF(descr);
5408+
return res;
5409+
}
5410+
53935411
static PyObject *
53945412
slot_tp_getattr_hook(PyObject *self, PyObject *name)
53955413
{
@@ -5409,24 +5427,39 @@ slot_tp_getattr_hook(PyObject *self, PyObject *name)
54095427
if (getattribute_str == NULL)
54105428
return NULL;
54115429
}
5430+
/* speed hack: we could use lookup_maybe, but that would resolve the
5431+
method fully for each attribute lookup for classes with
5432+
__getattr__, even when the attribute is present. So we use
5433+
_PyType_Lookup and create the method only when needed, with
5434+
call_attribute. */
54125435
getattr = _PyType_Lookup(tp, getattr_str);
54135436
if (getattr == NULL) {
54145437
/* No __getattr__ hook: use a simpler dispatcher */
54155438
tp->tp_getattro = slot_tp_getattro;
54165439
return slot_tp_getattro(self, name);
54175440
}
5441+
Py_INCREF(getattr);
5442+
/* speed hack: we could use lookup_maybe, but that would resolve the
5443+
method fully for each attribute lookup for classes with
5444+
__getattr__, even when self has the default __getattribute__
5445+
method. So we use _PyType_Lookup and create the method only when
5446+
needed, with call_attribute. */
54185447
getattribute = _PyType_Lookup(tp, getattribute_str);
54195448
if (getattribute == NULL ||
54205449
(Py_TYPE(getattribute) == &PyWrapperDescr_Type &&
54215450
((PyWrapperDescrObject *)getattribute)->d_wrapped ==
54225451
(void *)PyObject_GenericGetAttr))
54235452
res = PyObject_GenericGetAttr(self, name);
5424-
else
5425-
res = PyObject_CallFunctionObjArgs(getattribute, self, name, NULL);
5453+
else {
5454+
Py_INCREF(getattribute);
5455+
res = call_attribute(self, getattribute, name);
5456+
Py_DECREF(getattribute);
5457+
}
54265458
if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
54275459
PyErr_Clear();
5428-
res = PyObject_CallFunctionObjArgs(getattr, self, name, NULL);
5460+
res = call_attribute(self, getattr, name);
54295461
}
5462+
Py_DECREF(getattr);
54305463
return res;
54315464
}
54325465

0 commit comments

Comments
 (0)