Index: Lib/test/test_operator.py =================================================================== --- Lib/test/test_operator.py (revision 59958) +++ Lib/test/test_operator.py (working copy) @@ -386,6 +386,46 @@ raise SyntaxError self.failUnlessRaises(AttributeError, operator.attrgetter('foo'), C()) + # recursive gets + a = A() + a.name = 'arthur' + a.child = A() + a.child.name = 'thomas' + f = operator.attrgetter('child.name') + self.assertEqual(f(a), 'thomas') + self.assertRaises(AttributeError, f, a.child) + f = operator.attrgetter('name', 'child.name') + self.assertEqual(f(a), ('arthur', 'thomas')) + f = operator.attrgetter('name', 'child.name', 'child.child.name') + self.assertRaises(AttributeError, f, a) + + a.child.child = A() + a.child.child.name = 'johnson' + f = operator.attrgetter('child.child.name') + self.assertEqual(f(a), 'johnson') + f = operator.attrgetter('name', 'child.name', 'child.child.name') + self.assertEqual(f(a), ('arthur', 'thomas', 'johnson')) + def test_itemgetter(self): a = 'ABCDE' f = operator.itemgetter(2) Index: Modules/operator.c =================================================================== --- Modules/operator.c (revision 59958) +++ Modules/operator.c (working copy) @@ -496,6 +496,37 @@ } static PyObject * +getattr_chaser(PyObject *obj, PyObject *attr) +{ + PyObject *splitter, *sattrs, *val, *newval; + Py_ssize_t i, nattrs; + + splitter = PyString_FromString("."); + if(splitter == NULL) + return NULL; + + sattrs = PyUnicode_Split(attr, splitter, -1); + Py_DECREF(splitter); + if(sattrs == NULL) + return NULL; + + nattrs = PyList_GET_SIZE(sattrs); + + val = obj; + Py_INCREF(val); + for(i = 0; i < nattrs; i++) { + newval = PyObject_GetAttr(val, PyList_GET_ITEM(sattrs, i)); + Py_DECREF(val); + val = newval; + if(val == NULL) + break; + } + + Py_DECREF(sattrs); + return val; +} + +static PyObject * attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw) { PyObject *obj, *result; @@ -504,7 +535,7 @@ if (!PyArg_UnpackTuple(args, "attrgetter", 1, 1, &obj)) return NULL; if (ag->nattrs == 1) - return PyObject_GetAttr(obj, ag->attr); + return getattr_chaser(obj, ag->attr); assert(PyTuple_Check(ag->attr)); assert(PyTuple_GET_SIZE(ag->attr) == nattrs); @@ -516,7 +547,7 @@ for (i=0 ; i < nattrs ; i++) { PyObject *attr, *val; attr = PyTuple_GET_ITEM(ag->attr, i); - val = PyObject_GetAttr(obj, attr); + val = getattr_chaser(obj, attr); if (val == NULL) { Py_DECREF(result); return NULL; @@ -531,7 +562,9 @@ \n\ Return a callable object that fetches the given attribute(s) from its operand.\n\ After, f=attrgetter('name'), the call f(r) returns r.name.\n\ -After, g=attrgetter('name', 'date'), the call g(r) returns (r.name, r.date)."); +After, g=attrgetter('name', 'date'), the call g(r) returns (r.name, r.date).\n\ +After, h=attrgetter('name.first', 'name.last'), the call h(r) returns\n\ +(r.name.first, r.name.last)."); static PyTypeObject attrgetter_type = { PyVarObject_HEAD_INIT(NULL, 0)