bpo-35664: Optimize operator.itemgetter (GH-11435)

This commit is contained in:
Raymond Hettinger 2019-01-07 09:38:41 -07:00 committed by GitHub
parent 3f7983a25a
commit 2d53bed79c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 5 deletions

View File

@ -401,6 +401,19 @@ class OperatorTestCase:
self.assertEqual(operator.itemgetter(2,10,5)(data), ('2', '10', '5')) self.assertEqual(operator.itemgetter(2,10,5)(data), ('2', '10', '5'))
self.assertRaises(TypeError, operator.itemgetter(2, 'x', 5), data) self.assertRaises(TypeError, operator.itemgetter(2, 'x', 5), data)
# interesting indices
t = tuple('abcde')
self.assertEqual(operator.itemgetter(-1)(t), 'e')
self.assertEqual(operator.itemgetter(slice(2, 4))(t), ('c', 'd'))
# interesting sequences
class T(tuple):
'Tuple subclass'
pass
self.assertEqual(operator.itemgetter(0)(T('abc')), 'a')
self.assertEqual(operator.itemgetter(0)(['a', 'b', 'c']), 'a')
self.assertEqual(operator.itemgetter(0)(range(100, 200)), 100)
def test_methodcaller(self): def test_methodcaller(self):
operator = self.module operator = self.module
self.assertRaises(TypeError, operator.methodcaller) self.assertRaises(TypeError, operator.methodcaller)

View File

@ -0,0 +1,4 @@
Improve operator.itemgetter() performance by 33% with optimized argument
handling and with adding a fast path for the common case of a single
non-negative integer index into a tuple (which is the typical use case in
the standard library).

View File

@ -937,6 +937,7 @@ typedef struct {
PyObject_HEAD PyObject_HEAD
Py_ssize_t nitems; Py_ssize_t nitems;
PyObject *item; PyObject *item;
Py_ssize_t index; // -1 unless *item* is a single non-negative integer index
} itemgetterobject; } itemgetterobject;
static PyTypeObject itemgetter_type; static PyTypeObject itemgetter_type;
@ -948,6 +949,7 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
itemgetterobject *ig; itemgetterobject *ig;
PyObject *item; PyObject *item;
Py_ssize_t nitems; Py_ssize_t nitems;
Py_ssize_t index;
if (!_PyArg_NoKeywords("itemgetter", kwds)) if (!_PyArg_NoKeywords("itemgetter", kwds))
return NULL; return NULL;
@ -967,6 +969,21 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Py_INCREF(item); Py_INCREF(item);
ig->item = item; ig->item = item;
ig->nitems = nitems; ig->nitems = nitems;
ig->index = -1;
if (PyLong_CheckExact(item)) {
index = PyLong_AsSsize_t(item);
if (index < 0) {
/* If we get here, then either the index conversion failed
* due to being out of range, or the index was a negative
* integer. Either way, we clear any possible exception
* and fall back to the slow path, where ig->index is -1.
*/
PyErr_Clear();
}
else {
ig->index = index;
}
}
PyObject_GC_Track(ig); PyObject_GC_Track(ig);
return (PyObject *)ig; return (PyObject *)ig;
@ -993,12 +1010,27 @@ itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw)
PyObject *obj, *result; PyObject *obj, *result;
Py_ssize_t i, nitems=ig->nitems; Py_ssize_t i, nitems=ig->nitems;
if (!_PyArg_NoKeywords("itemgetter", kw)) assert(PyTuple_CheckExact(args));
return NULL; if (kw == NULL && PyTuple_GET_SIZE(args) == 1) {
if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &obj)) obj = PyTuple_GET_ITEM(args, 0);
return NULL; }
if (nitems == 1) else {
if (!_PyArg_NoKeywords("itemgetter", kw))
return NULL;
if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &obj))
return NULL;
}
if (nitems == 1) {
if (ig->index >= 0
&& PyTuple_CheckExact(obj)
&& ig->index < PyTuple_GET_SIZE(obj))
{
result = PyTuple_GET_ITEM(obj, ig->index);
Py_INCREF(result);
return result;
}
return PyObject_GetItem(obj, ig->item); return PyObject_GetItem(obj, ig->item);
}
assert(PyTuple_Check(ig->item)); assert(PyTuple_Check(ig->item));
assert(PyTuple_GET_SIZE(ig->item) == nitems); assert(PyTuple_GET_SIZE(ig->item) == nitems);