Skip to content

Commit 2d53bed

Browse files
authored
bpo-35664: Optimize operator.itemgetter (pythonGH-11435)
1 parent 3f7983a commit 2d53bed

3 files changed

Lines changed: 54 additions & 5 deletions

File tree

Lib/test/test_operator.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,19 @@ def __getitem__(self, name):
401401
self.assertEqual(operator.itemgetter(2,10,5)(data), ('2', '10', '5'))
402402
self.assertRaises(TypeError, operator.itemgetter(2, 'x', 5), data)
403403

404+
# interesting indices
405+
t = tuple('abcde')
406+
self.assertEqual(operator.itemgetter(-1)(t), 'e')
407+
self.assertEqual(operator.itemgetter(slice(2, 4))(t), ('c', 'd'))
408+
409+
# interesting sequences
410+
class T(tuple):
411+
'Tuple subclass'
412+
pass
413+
self.assertEqual(operator.itemgetter(0)(T('abc')), 'a')
414+
self.assertEqual(operator.itemgetter(0)(['a', 'b', 'c']), 'a')
415+
self.assertEqual(operator.itemgetter(0)(range(100, 200)), 100)
416+
404417
def test_methodcaller(self):
405418
operator = self.module
406419
self.assertRaises(TypeError, operator.methodcaller)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Improve operator.itemgetter() performance by 33% with optimized argument
2+
handling and with adding a fast path for the common case of a single
3+
non-negative integer index into a tuple (which is the typical use case in
4+
the standard library).

Modules/_operator.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ typedef struct {
937937
PyObject_HEAD
938938
Py_ssize_t nitems;
939939
PyObject *item;
940+
Py_ssize_t index; // -1 unless *item* is a single non-negative integer index
940941
} itemgetterobject;
941942

942943
static PyTypeObject itemgetter_type;
@@ -948,6 +949,7 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
948949
itemgetterobject *ig;
949950
PyObject *item;
950951
Py_ssize_t nitems;
952+
Py_ssize_t index;
951953

952954
if (!_PyArg_NoKeywords("itemgetter", kwds))
953955
return NULL;
@@ -967,6 +969,21 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
967969
Py_INCREF(item);
968970
ig->item = item;
969971
ig->nitems = nitems;
972+
ig->index = -1;
973+
if (PyLong_CheckExact(item)) {
974+
index = PyLong_AsSsize_t(item);
975+
if (index < 0) {
976+
/* If we get here, then either the index conversion failed
977+
* due to being out of range, or the index was a negative
978+
* integer. Either way, we clear any possible exception
979+
* and fall back to the slow path, where ig->index is -1.
980+
*/
981+
PyErr_Clear();
982+
}
983+
else {
984+
ig->index = index;
985+
}
986+
}
970987

971988
PyObject_GC_Track(ig);
972989
return (PyObject *)ig;
@@ -993,12 +1010,27 @@ itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw)
9931010
PyObject *obj, *result;
9941011
Py_ssize_t i, nitems=ig->nitems;
9951012

996-
if (!_PyArg_NoKeywords("itemgetter", kw))
997-
return NULL;
998-
if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &obj))
999-
return NULL;
1000-
if (nitems == 1)
1013+
assert(PyTuple_CheckExact(args));
1014+
if (kw == NULL && PyTuple_GET_SIZE(args) == 1) {
1015+
obj = PyTuple_GET_ITEM(args, 0);
1016+
}
1017+
else {
1018+
if (!_PyArg_NoKeywords("itemgetter", kw))
1019+
return NULL;
1020+
if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &obj))
1021+
return NULL;
1022+
}
1023+
if (nitems == 1) {
1024+
if (ig->index >= 0
1025+
&& PyTuple_CheckExact(obj)
1026+
&& ig->index < PyTuple_GET_SIZE(obj))
1027+
{
1028+
result = PyTuple_GET_ITEM(obj, ig->index);
1029+
Py_INCREF(result);
1030+
return result;
1031+
}
10011032
return PyObject_GetItem(obj, ig->item);
1033+
}
10021034

10031035
assert(PyTuple_Check(ig->item));
10041036
assert(PyTuple_GET_SIZE(ig->item) == nitems);

0 commit comments

Comments
 (0)