Index: Doc/library/operator.rst =================================================================== --- Doc/library/operator.rst (Revision 60093) +++ Doc/library/operator.rst (Arbeitskopie) @@ -499,16 +499,22 @@ Return a callable object that fetches *attr* from its operand. If more than one attribute is requested, returns a tuple of attributes. After, - ``f=attrgetter('name')``, the call ``f(b)`` returns ``b.name``. After, - ``f=attrgetter('name', 'date')``, the call ``f(b)`` returns ``(b.name, + ``f = attrgetter('name')``, the call ``f(b)`` returns ``b.name``. After, + ``f = attrgetter('name', 'date')``, the call ``f(b)`` returns ``(b.name, b.date)``. + The attribute names can also contain dots; after ``f = attrgetter('date.month')``, + the call ``f(b)`` returns ``b.date.month``. + .. versionadded:: 2.4 .. versionchanged:: 2.5 Added support for multiple attributes. + .. versionchanged:: 2.6 + Added support for dotted attributes. + .. function:: itemgetter(item[, args...]) Return a callable object that fetches *item* from its operand. If more than one Index: Lib/test/test_operator.py =================================================================== --- Lib/test/test_operator.py (Revision 60093) +++ Lib/test/test_operator.py (Arbeitskopie) @@ -386,6 +386,26 @@ 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 60093) +++ Modules/operator.c (Arbeitskopie) @@ -496,6 +496,49 @@ } static PyObject * +dotted_getattr(PyObject *obj, PyObject *attr) +{ + char *s, *p; + +#ifdef Py_USING_UNICODE + if (PyUnicode_Check(attr)) { + attr = _PyUnicode_AsDefaultEncodedString(attr, NULL); + if (attr == NULL) + return NULL; + } +#endif + + if (!PyString_Check(attr)) { + PyErr_SetString(PyExc_TypeError, + "attribute name must be a string"); + return NULL; + } + + s = PyString_AS_STRING(attr); + Py_INCREF(obj); + for (;;) { + PyObject *newobj, *str; + p = strchr(s, '.'); + str = p ? PyString_FromStringAndSize(s, (p-s)) : + PyString_FromString(s); + if (str == NULL) { + Py_DECREF(obj); + return NULL; + } + newobj = PyObject_GetAttr(obj, str); + Py_DECREF(str); + Py_DECREF(obj); + if (newobj == NULL) + return NULL; + obj = newobj; + if (p == NULL) break; + s = p+1; + } + + return obj; +} + +static PyObject * attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw) { PyObject *obj, *result; @@ -504,7 +547,7 @@ if (!PyArg_UnpackTuple(args, "attrgetter", 1, 1, &obj)) return NULL; if (ag->nattrs == 1) - return PyObject_GetAttr(obj, ag->attr); + return dotted_getattr(obj, ag->attr); assert(PyTuple_Check(ag->attr)); assert(PyTuple_GET_SIZE(ag->attr) == nattrs); @@ -516,7 +559,7 @@ for (i=0 ; i < nattrs ; i++) { PyObject *attr, *val; attr = PyTuple_GET_ITEM(ag->attr, i); - val = PyObject_GetAttr(obj, attr); + val = dotted_getattr(obj, attr); if (val == NULL) { Py_DECREF(result); return NULL; @@ -531,7 +574,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)