[Python-checkins] bpo-35664: Optimize operator.itemgetter (GH-11435)

Raymond Hettinger webhook-mailer at python.org
Mon Jan 7 11:38:45 EST 2019


https://github.com/python/cpython/commit/2d53bed79c1953390f85b191c72855e457e09305
commit: 2d53bed79c1953390f85b191c72855e457e09305
branch: master
author: Raymond Hettinger <rhettinger at users.noreply.github.com>
committer: GitHub <noreply at github.com>
date: 2019-01-07T09:38:41-07:00
summary:

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

files:
A Misc/NEWS.d/next/Library/2019-01-04-22-18-25.bpo-35664.Z-Gyyj.rst
M Lib/test/test_operator.py
M Modules/_operator.c

diff --git a/Lib/test/test_operator.py b/Lib/test/test_operator.py
index 6254091e78fe..f46d94a22671 100644
--- a/Lib/test/test_operator.py
+++ b/Lib/test/test_operator.py
@@ -401,6 +401,19 @@ def __getitem__(self, name):
         self.assertEqual(operator.itemgetter(2,10,5)(data), ('2', '10', '5'))
         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):
         operator = self.module
         self.assertRaises(TypeError, operator.methodcaller)
diff --git a/Misc/NEWS.d/next/Library/2019-01-04-22-18-25.bpo-35664.Z-Gyyj.rst b/Misc/NEWS.d/next/Library/2019-01-04-22-18-25.bpo-35664.Z-Gyyj.rst
new file mode 100644
index 000000000000..f4acc5ae57e7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-01-04-22-18-25.bpo-35664.Z-Gyyj.rst
@@ -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).
diff --git a/Modules/_operator.c b/Modules/_operator.c
index 3bf8c1276d7b..d6c6a18d81b4 100644
--- a/Modules/_operator.c
+++ b/Modules/_operator.c
@@ -937,6 +937,7 @@ typedef struct {
     PyObject_HEAD
     Py_ssize_t nitems;
     PyObject *item;
+    Py_ssize_t index; // -1 unless *item* is a single non-negative integer index
 } itemgetterobject;
 
 static PyTypeObject itemgetter_type;
@@ -948,6 +949,7 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     itemgetterobject *ig;
     PyObject *item;
     Py_ssize_t nitems;
+    Py_ssize_t index;
 
     if (!_PyArg_NoKeywords("itemgetter", kwds))
         return NULL;
@@ -967,6 +969,21 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     Py_INCREF(item);
     ig->item = item;
     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);
     return (PyObject *)ig;
@@ -993,12 +1010,27 @@ itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw)
     PyObject *obj, *result;
     Py_ssize_t i, nitems=ig->nitems;
 
-    if (!_PyArg_NoKeywords("itemgetter", kw))
-        return NULL;
-    if (!PyArg_UnpackTuple(args, "itemgetter", 1, 1, &obj))
-        return NULL;
-    if (nitems == 1)
+    assert(PyTuple_CheckExact(args));
+    if (kw == NULL && PyTuple_GET_SIZE(args) == 1) {
+        obj = PyTuple_GET_ITEM(args, 0);
+    }
+    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);
+    }
 
     assert(PyTuple_Check(ig->item));
     assert(PyTuple_GET_SIZE(ig->item) == nitems);



More information about the Python-checkins mailing list