diff options
-rw-r--r-- | Doc/library/operator.rst | 10 | ||||
-rw-r--r-- | Lib/test/test_operator.py | 20 | ||||
-rw-r--r-- | Misc/NEWS | 2 | ||||
-rw-r--r-- | Modules/operator.c | 51 |
4 files changed, 78 insertions, 5 deletions
diff --git a/Doc/library/operator.rst b/Doc/library/operator.rst index ea4d328ffe..5527dc2c8f 100644 --- a/Doc/library/operator.rst +++ b/Doc/library/operator.rst @@ -499,15 +499,21 @@ expect a function argument. 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...]) diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py index a60ceb609e..3f3ea0033e 100644 --- a/Lib/test/test_operator.py +++ b/Lib/test/test_operator.py @@ -386,6 +386,26 @@ class OperatorTestCase(unittest.TestCase): raise SyntaxError self.failUnlessRaises(SyntaxError, 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) @@ -1196,6 +1196,8 @@ Library Extension Modules ----------------- +- Patch #1826: operator.attrgetter() now supports dotted attribute paths. + - Patch #1957: syslogmodule: Release GIL when calling syslog(3) - #2112: mmap.error is now a subclass of EnvironmentError and not a diff --git a/Modules/operator.c b/Modules/operator.c index b6c3d000e8..4d4a9401b6 100644 --- a/Modules/operator.c +++ b/Modules/operator.c @@ -496,6 +496,49 @@ attrgetter_traverse(attrgetterobject *ag, visitproc visit, void *arg) } 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 @@ attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw) 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 @@ attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw) 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 @@ PyDoc_STRVAR(attrgetter_doc, \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) |