Kaydet (Commit) 984f9bb7 authored tarafından Raymond Hettinger's avatar Raymond Hettinger

operator.itemgetter() and operator.attrgetter() now support extraction

of multiple fields.  This provides direct support for sorting by
multiple keys.
üst 6a3f4f7b
...@@ -306,24 +306,31 @@ as arguments for \function{map()}, \function{sorted()}, ...@@ -306,24 +306,31 @@ as arguments for \function{map()}, \function{sorted()},
\method{itertools.groupby()}, or other functions that expect a \method{itertools.groupby()}, or other functions that expect a
function argument. function argument.
\begin{funcdesc}{attrgetter}{attr} \begin{funcdesc}{attrgetter}{attr\optional{, args...}}
Return a callable object that fetches \var{attr} from its operand. Return a callable object that fetches \var{attr} from its operand.
If more than one attribute is requested, returns a tuple of attributes.
After, \samp{f=attrgetter('name')}, the call \samp{f(b)} returns After, \samp{f=attrgetter('name')}, the call \samp{f(b)} returns
\samp{b.name}. \samp{b.name}. After, \samp{f=attrgetter('name', 'date')}, the call
\samp{f(b)} returns \samp{(b.name, b.date)}.
\versionadded{2.4} \versionadded{2.4}
\versionchanged[Added support for multiple attributes]{2.5}
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{itemgetter}{item} \begin{funcdesc}{itemgetter}{item\optional{, args...}}
Return a callable object that fetches \var{item} from its operand. Return a callable object that fetches \var{item} from its operand.
If more than one item is requested, returns a tuple of items.
After, \samp{f=itemgetter(2)}, the call \samp{f(b)} returns After, \samp{f=itemgetter(2)}, the call \samp{f(b)} returns
\samp{b[2]}. \samp{b[2]}.
After, \samp{f=itemgetter(2,5,3)}, the call \samp{f(b)} returns
\samp{(b[2], b[5], b[3])}.
\versionadded{2.4} \versionadded{2.4}
\versionchanged[Added support for multiple item extraction]{2.5}
\end{funcdesc} \end{funcdesc}
Examples: Examples:
\begin{verbatim} \begin{verbatim}
>>> from operator import * >>> from operator import itemgetter
>>> inventory = [('apple', 3), ('banana', 2), ('pear', 5), ('orange', 1)] >>> inventory = [('apple', 3), ('banana', 2), ('pear', 5), ('orange', 1)]
>>> getcount = itemgetter(1) >>> getcount = itemgetter(1)
>>> map(getcount, inventory) >>> map(getcount, inventory)
......
...@@ -324,7 +324,14 @@ class OperatorTestCase(unittest.TestCase): ...@@ -324,7 +324,14 @@ class OperatorTestCase(unittest.TestCase):
f = operator.attrgetter(2) f = operator.attrgetter(2)
self.assertRaises(TypeError, f, a) self.assertRaises(TypeError, f, a)
self.assertRaises(TypeError, operator.attrgetter) self.assertRaises(TypeError, operator.attrgetter)
self.assertRaises(TypeError, operator.attrgetter, 1, 2)
# multiple gets
record = A()
record.x = 'X'
record.y = 'Y'
record.z = 'Z'
self.assertEqual(operator.attrgetter('x','z','y')(record), ('X', 'Z', 'Y'))
self.assertRaises(TypeError, operator.attrgetter('x', (), 'y'), record)
class C(object): class C(object):
def __getattr(self, name): def __getattr(self, name):
...@@ -346,7 +353,6 @@ class OperatorTestCase(unittest.TestCase): ...@@ -346,7 +353,6 @@ class OperatorTestCase(unittest.TestCase):
f = operator.itemgetter('name') f = operator.itemgetter('name')
self.assertRaises(TypeError, f, a) self.assertRaises(TypeError, f, a)
self.assertRaises(TypeError, operator.itemgetter) self.assertRaises(TypeError, operator.itemgetter)
self.assertRaises(TypeError, operator.itemgetter, 1, 2)
d = dict(key='val') d = dict(key='val')
f = operator.itemgetter('key') f = operator.itemgetter('key')
...@@ -361,9 +367,29 @@ class OperatorTestCase(unittest.TestCase): ...@@ -361,9 +367,29 @@ class OperatorTestCase(unittest.TestCase):
self.assertEqual(sorted(inventory, key=getcount), self.assertEqual(sorted(inventory, key=getcount),
[('orange', 1), ('banana', 2), ('apple', 3), ('pear', 5)]) [('orange', 1), ('banana', 2), ('apple', 3), ('pear', 5)])
def test_main(): # multiple gets
test_support.run_unittest(OperatorTestCase) data = map(str, range(20))
self.assertEqual(operator.itemgetter(2,10,5)(data), ('2', '10', '5'))
self.assertRaises(TypeError, operator.itemgetter(2, 'x', 5), data)
def test_main(verbose=None):
import sys
test_classes = (
OperatorTestCase,
)
test_support.run_unittest(*test_classes)
# verify reference counting
if verbose and hasattr(sys, "gettotalrefcount"):
import gc
counts = [None] * 5
for i in xrange(len(counts)):
test_support.run_unittest(*test_classes)
gc.collect()
counts[i] = sys.gettotalrefcount()
print counts
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main(verbose=True)
...@@ -47,6 +47,10 @@ Core and builtins ...@@ -47,6 +47,10 @@ Core and builtins
Extension Modules Extension Modules
----------------- -----------------
- operator.itemgetter() and operator.attrgetter() now support retrieving
multiple fields. This provides direct support for sorting on multiple
keys (primary, secondary, etc).
- os.access now supports Unicode path names on non-Win32 systems. - os.access now supports Unicode path names on non-Win32 systems.
- Patches #925152, #1118602: Avoid reading after the end of the buffer - Patches #925152, #1118602: Avoid reading after the end of the buffer
......
...@@ -256,6 +256,7 @@ spam2(ge,__ge__, "ge(a, b) -- Same as a>=b.") ...@@ -256,6 +256,7 @@ spam2(ge,__ge__, "ge(a, b) -- Same as a>=b.")
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
int nitems;
PyObject *item; PyObject *item;
} itemgetterobject; } itemgetterobject;
...@@ -266,9 +267,14 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) ...@@ -266,9 +267,14 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{ {
itemgetterobject *ig; itemgetterobject *ig;
PyObject *item; PyObject *item;
int nitems;
if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &item)) nitems = PyTuple_GET_SIZE(args);
return NULL; if (nitems <= 1) {
if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &item))
return NULL;
} else
item = args;
/* create itemgetterobject structure */ /* create itemgetterobject structure */
ig = PyObject_GC_New(itemgetterobject, &itemgetter_type); ig = PyObject_GC_New(itemgetterobject, &itemgetter_type);
...@@ -277,6 +283,7 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) ...@@ -277,6 +283,7 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Py_INCREF(item); Py_INCREF(item);
ig->item = item; ig->item = item;
ig->nitems = nitems;
PyObject_GC_Track(ig); PyObject_GC_Track(ig);
return (PyObject *)ig; return (PyObject *)ig;
...@@ -301,18 +308,40 @@ itemgetter_traverse(itemgetterobject *ig, visitproc visit, void *arg) ...@@ -301,18 +308,40 @@ itemgetter_traverse(itemgetterobject *ig, visitproc visit, void *arg)
static PyObject * static PyObject *
itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw) itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw)
{ {
PyObject * obj; PyObject *obj, *result;
int i, nitems=ig->nitems;
if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &obj)) if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &obj))
return NULL; return NULL;
return PyObject_GetItem(obj, ig->item); if (nitems == 1)
return PyObject_GetItem(obj, ig->item);
assert(PyTuple_Check(ig->item));
assert(PyTuple_GET_SIZE(ig->item) == nitems);
result = PyTuple_New(nitems);
if (result == NULL)
return NULL;
for (i=0 ; i < nitems ; i++) {
PyObject *item, *val;
item = PyTuple_GET_ITEM(ig->item, i);
val = PyObject_GetItem(obj, item);
if (val == NULL) {
Py_DECREF(result);
return NULL;
}
PyTuple_SET_ITEM(result, i, val);
}
return result;
} }
PyDoc_STRVAR(itemgetter_doc, PyDoc_STRVAR(itemgetter_doc,
"itemgetter(item) --> itemgetter object\n\ "itemgetter(item, ...) --> itemgetter object\n\
\n\ \n\
Return a callable object that fetches the given item from its operand.\n\ Return a callable object that fetches the given item(s) from its operand.\n\
After, f=itemgetter(2), the call f(b) returns b[2]."); After, f=itemgetter(2), the call f(r) returns r[2].\n\
After, g=itemgetter(2,5,3), the call g(r) returns (r[2], r[5], r[3])");
static PyTypeObject itemgetter_type = { static PyTypeObject itemgetter_type = {
PyObject_HEAD_INIT(NULL) PyObject_HEAD_INIT(NULL)
...@@ -363,6 +392,7 @@ static PyTypeObject itemgetter_type = { ...@@ -363,6 +392,7 @@ static PyTypeObject itemgetter_type = {
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
int nattrs;
PyObject *attr; PyObject *attr;
} attrgetterobject; } attrgetterobject;
...@@ -373,9 +403,14 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) ...@@ -373,9 +403,14 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{ {
attrgetterobject *ag; attrgetterobject *ag;
PyObject *attr; PyObject *attr;
int nattrs;
if (!PyArg_UnpackTuple(args, "attrgetter", 1, 1, &attr)) nattrs = PyTuple_GET_SIZE(args);
return NULL; if (nattrs <= 1) {
if (!PyArg_UnpackTuple(args, "attrgetter", 1, 1, &attr))
return NULL;
} else
attr = args;
/* create attrgetterobject structure */ /* create attrgetterobject structure */
ag = PyObject_GC_New(attrgetterobject, &attrgetter_type); ag = PyObject_GC_New(attrgetterobject, &attrgetter_type);
...@@ -384,6 +419,7 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) ...@@ -384,6 +419,7 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Py_INCREF(attr); Py_INCREF(attr);
ag->attr = attr; ag->attr = attr;
ag->nattrs = nattrs;
PyObject_GC_Track(ag); PyObject_GC_Track(ag);
return (PyObject *)ag; return (PyObject *)ag;
...@@ -408,18 +444,40 @@ attrgetter_traverse(attrgetterobject *ag, visitproc visit, void *arg) ...@@ -408,18 +444,40 @@ attrgetter_traverse(attrgetterobject *ag, visitproc visit, void *arg)
static PyObject * static PyObject *
attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw) attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw)
{ {
PyObject * obj; PyObject *obj, *result;
int i, nattrs=ag->nattrs;
if (!PyArg_UnpackTuple(args, "attrgetter", 1, 1, &obj)) if (!PyArg_UnpackTuple(args, "attrgetter", 1, 1, &obj))
return NULL; return NULL;
return PyObject_GetAttr(obj, ag->attr); if (ag->nattrs == 1)
return PyObject_GetAttr(obj, ag->attr);
assert(PyTuple_Check(ag->attr));
assert(PyTuple_GET_SIZE(ag->attr) == nattrs);
result = PyTuple_New(nattrs);
if (result == NULL)
return NULL;
for (i=0 ; i < nattrs ; i++) {
PyObject *attr, *val;
attr = PyTuple_GET_ITEM(ag->attr, i);
val = PyObject_GetAttr(obj, attr);
if (val == NULL) {
Py_DECREF(result);
return NULL;
}
PyTuple_SET_ITEM(result, i, val);
}
return result;
} }
PyDoc_STRVAR(attrgetter_doc, PyDoc_STRVAR(attrgetter_doc,
"attrgetter(attr) --> attrgetter object\n\ "attrgetter(attr, ...) --> attrgetter object\n\
\n\ \n\
Return a callable object that fetches the given attribute from its operand.\n\ Return a callable object that fetches the given attribute(s) from its operand.\n\
After, f=attrgetter('name'), the call f(b) returns b.name."); 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).");
static PyTypeObject attrgetter_type = { static PyTypeObject attrgetter_type = {
PyObject_HEAD_INIT(NULL) PyObject_HEAD_INIT(NULL)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment