https://github.com/python/cpython/commit/7e3290d94427cdbfe0483f0a99d589b784d77c1a
commit: 7e3290d94427cdbfe0483f0a99d589b784d77c1a
branch: main
author: Cody Maloney <[email protected]>
committer: cmaloney <[email protected]>
date: 2026-11-03T14:39:14-08:00
summary:

gh-87613: Use AC and AC vectorcall for filter (#160792)

gh-87613: Use AC vectorcall for filter

Move filter.__new__ to Argument Clinic with @vectorcall, dropping the
hand-written filter_new and filter_vectorcall. The generated vectorcall
name matches the hand-written one so the tp_vectorcall slot is unchanged,
and argument error messages are unchanged on both paths.

pyperf microbenchmark (PGO+LTO, run serially), no significant change on
the vectorcall path:

| Benchmark           | base    | filter                |
|---------------------|:-------:|:---------------------:|
| `filter.__new__`    | 45.4 ns | 42.7 ns: 1.06x faster |
| filter(bool, list)  | 32.3 ns | 32.8 ns: 1.01x slower |
| filter(None, range) | 30.6 ns | 31.8 ns: 1.04x slower |
| Geometric mean      | (ref)   | 1.01x faster          |

Benchmark hidden because not significant (3): subclass(None, tuple),
filter(None, tuple), list(filter(None, list))

filter.__new__ drops PyArg_UnpackTuple for inline clinic parsing.

Co-authored-by: Claude Opus 5.5 <[email protected]>

files:
M Python/bltinmodule.c
M Python/clinic/bltinmodule.c.h

diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index b04715ef30b58cc..e58c37d1d07ac7d 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -566,65 +566,37 @@ typedef struct {
 
 #define _filterobject_CAST(op)      ((filterobject *)(op))
 
-static PyObject *
-filter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
-{
-    PyObject *func, *seq;
-    PyObject *it;
-    filterobject *lz;
-
-    if ((type == &PyFilter_Type || type->tp_init == PyFilter_Type.tp_init) &&
-        !_PyArg_NoKeywords("filter", kwds))
-        return NULL;
-
-    if (!PyArg_UnpackTuple(args, "filter", 2, 2, &func, &seq))
-        return NULL;
+/*[clinic input]
+class filter "filterobject *" "&PyFilter_Type"
+@vectorcall
+@classmethod
+filter.__new__ as filter_new
 
-    /* Get iterator. */
-    it = PyObject_GetIter(seq);
-    if (it == NULL)
-        return NULL;
+    function as func: object
+    iterable as seq: object
+    /
 
-    /* create filterobject structure */
-    lz = (filterobject *)type->tp_alloc(type, 0);
-    if (lz == NULL) {
-        Py_DECREF(it);
-        return NULL;
-    }
+Return an iterator over items of iterable where function(item) is true.
 
-    lz->func = Py_NewRef(func);
-    lz->it = it;
-
-    return (PyObject *)lz;
-}
+If function is None, return the items that are true.
+[clinic start generated code]*/
 
 static PyObject *
-filter_vectorcall(PyObject *type, PyObject * const*args,
-                size_t nargsf, PyObject *kwnames)
+filter_new_impl(PyTypeObject *type, PyObject *func, PyObject *seq)
+/*[clinic end generated code: output=2134a9f0032a6037 input=57836123a59c4d91]*/
 {
-    PyTypeObject *tp = _PyType_CAST(type);
-    if (tp == &PyFilter_Type && !_PyArg_NoKwnames("filter", kwnames)) {
-        return NULL;
-    }
-
-    Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
-    if (!_PyArg_CheckPositional("filter", nargs, 2, 2)) {
-        return NULL;
-    }
-
-    PyObject *it = PyObject_GetIter(args[1]);
+    PyObject *it = PyObject_GetIter(seq);
     if (it == NULL) {
         return NULL;
     }
 
-    filterobject *lz = (filterobject *)tp->tp_alloc(tp, 0);
-
+    filterobject *lz = (filterobject *)type->tp_alloc(type, 0);
     if (lz == NULL) {
         Py_DECREF(it);
         return NULL;
     }
 
-    lz->func = Py_NewRef(args[0]);
+    lz->func = Py_NewRef(func);
     lz->it = it;
 
     return (PyObject *)lz;
@@ -699,14 +671,6 @@ static PyMethodDef filter_methods[] = {
     {NULL,           NULL}           /* sentinel */
 };
 
-PyDoc_STRVAR(filter_doc,
-"filter(function, iterable, /)\n\
---\n\
-\n\
-Return an iterator yielding those items of iterable for which\n\
-function(item) is true.  If function is None, return the items that\n\
-are true.");
-
 PyTypeObject PyFilter_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
     "filter",                           /* tp_name */
@@ -730,7 +694,7 @@ PyTypeObject PyFilter_Type = {
     0,                                  /* tp_as_buffer */
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
         Py_TPFLAGS_BASETYPE,            /* tp_flags */
-    filter_doc,                         /* tp_doc */
+    filter_new__doc__,                  /* tp_doc */
     filter_traverse,                    /* tp_traverse */
     0,                                  /* tp_clear */
     0,                                  /* tp_richcompare */
@@ -749,7 +713,7 @@ PyTypeObject PyFilter_Type = {
     PyType_GenericAlloc,                /* tp_alloc */
     filter_new,                         /* tp_new */
     PyObject_GC_Del,                    /* tp_free */
-    .tp_vectorcall = filter_vectorcall
+    .tp_vectorcall = filter_vectorcall,
 };
 
 
diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h
index 09ae184445f7d5d..46d24cbcecdb2c5 100644
--- a/Python/clinic/bltinmodule.c.h
+++ b/Python/clinic/bltinmodule.c.h
@@ -279,6 +279,67 @@ PyDoc_STRVAR(builtin_callable__doc__,
 #define BUILTIN_CALLABLE_METHODDEF    \
     {"callable", (PyCFunction)builtin_callable, METH_O, 
builtin_callable__doc__},
 
+PyDoc_STRVAR(filter_new__doc__,
+"filter(function, iterable, /)\n"
+"--\n"
+"\n"
+"Return an iterator over items of iterable where function(item) is true.\n"
+"\n"
+"If function is None, return the items that are true.");
+
+static PyObject *
+filter_new_impl(PyTypeObject *type, PyObject *func, PyObject *seq);
+
+static PyObject *
+filter_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    PyObject *return_value = NULL;
+    PyTypeObject *base_tp = &PyFilter_Type;
+    PyObject *func;
+    PyObject *seq;
+
+    if ((type == base_tp || type->tp_init == base_tp->tp_init) &&
+        !_PyArg_NoKeywords("filter", kwargs)) {
+        goto exit;
+    }
+    if (!_PyArg_CheckPositional("filter", PyTuple_GET_SIZE(args), 2, 2)) {
+        goto exit;
+    }
+    func = PyTuple_GET_ITEM(args, 0);
+    seq = PyTuple_GET_ITEM(args, 1);
+    return_value = filter_new_impl(type, func, seq);
+
+exit:
+    return return_value;
+}
+
+static PyObject *
+filter_vectorcall(PyObject *type, PyObject *const *args,
+    size_t nargsf, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
+    PyObject *func;
+    PyObject *seq;
+
+    assert(_PyType_CAST(type)->tp_new == filter_new);
+    /* Make sure the type object is immutable: the generated
+     * vectorcall doesn't deal e.g. with users reassigning __init__. */
+    assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE));
+    if (!_PyArg_NoKwnames("filter", kwnames)) {
+        goto exit;
+    }
+    if (!_PyArg_CheckPositional("filter", nargs, 2, 2)) {
+        goto exit;
+    }
+    func = args[0];
+    seq = args[1];
+    return_value = filter_new_impl(_PyType_CAST(type), func, seq);
+
+exit:
+    return return_value;
+}
+
 PyDoc_STRVAR(builtin_format__doc__,
 "format($module, value, format_spec=\'\', /)\n"
 "--\n"
@@ -1600,4 +1661,4 @@ zip_vectorcall(PyObject *type, PyObject *const *args,
         kwnames ? PyTuple_GET_SIZE(kwnames) : 0,
         NULL, kwnames);
 }
-/*[clinic end generated code: output=ef3ea633d4ed8708 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=a67fa5cf74c85d07 input=a9049054013a1b77]*/

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to