https://github.com/python/cpython/commit/972cfaae3f1349a2b822a0003e96e980c59a402f
commit: 972cfaae3f1349a2b822a0003e96e980c59a402f
branch: main
author: Marc Mueller <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-09-26T13:00:10-07:00
summary:

gh-138912: Add fast path for match class patterns without sub-patterns (#144820)

Co-authored-by: Pieter Eendebak <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-16-19-22.gh-issue-138912.TKMC9K.rst
M Doc/library/dis.rst
M Include/internal/pycore_intrinsics.h
M Lib/test/test_capi/test_opt.py
M Python/codegen.c
M Python/intrinsics.c

diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst
index ced19268dea2364..73e77f4707cf92a 100644
--- a/Doc/library/dis.rst
+++ b/Doc/library/dis.rst
@@ -1899,32 +1899,38 @@ iterations of the loop.
 
    The operand determines which intrinsic function is called:
 
-   
+------------------------------------------+-----------------------------------+
-   | Operand                                  | Description                    
   |
-   
+==========================================+===================================+
-   | ``INTRINSIC_2_INVALID``                  | Not valid                      
   |
-   
+------------------------------------------+-----------------------------------+
-   | ``INTRINSIC_PREP_RERAISE_STAR``          | Calculates the                 
   |
-   |                                          | :exc:`ExceptionGroup` to raise 
   |
-   |                                          | from a ``try-except*``.        
   |
-   
+------------------------------------------+-----------------------------------+
-   | ``INTRINSIC_TYPEVAR_WITH_BOUND``         | Creates a 
:class:`typing.TypeVar` |
-   |                                          | with a bound.                  
   |
-   
+------------------------------------------+-----------------------------------+
-   | ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS``   | Creates a                      
   |
-   |                                          | :class:`typing.TypeVar` with   
   |
-   |                                          | constraints.                   
   |
-   
+------------------------------------------+-----------------------------------+
-   | ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS``   | Sets the ``__type_params__``   
   |
-   |                                          | attribute of a function.       
   |
-   
+------------------------------------------+-----------------------------------+
-   | ``INTRINSIC_ADD_CONDITIONAL_ANNOTATION`` | Adds an annotation index to 
the   |
-   |                                          | 
``__conditional_annotations__``   |
-   |                                          | set.                           
   |
-   
+------------------------------------------+-----------------------------------+
+   
+------------------------------------------+-----------------------------------------+
+   | Operand                                  | Description                    
         |
+   
+==========================================+=========================================+
+   | ``INTRINSIC_2_INVALID``                  | Not valid                      
         |
+   
+------------------------------------------+-----------------------------------------+
+   | ``INTRINSIC_PREP_RERAISE_STAR``          | Calculates the                 
         |
+   |                                          | :exc:`ExceptionGroup` to raise 
         |
+   |                                          | from a ``try-except*``.        
         |
+   
+------------------------------------------+-----------------------------------------+
+   | ``INTRINSIC_TYPEVAR_WITH_BOUND``         | Creates a 
:class:`typing.TypeVar`       |
+   |                                          | with a bound.                  
         |
+   
+------------------------------------------+-----------------------------------------+
+   | ``INTRINSIC_TYPEVAR_WITH_CONSTRAINTS``   | Creates a                      
         |
+   |                                          | :class:`typing.TypeVar` with   
         |
+   |                                          | constraints.                   
         |
+   
+------------------------------------------+-----------------------------------------+
+   | ``INTRINSIC_SET_FUNCTION_TYPE_PARAMS``   | Sets the ``__type_params__``   
         |
+   |                                          | attribute of a function.       
         |
+   
+------------------------------------------+-----------------------------------------+
+   | ``INTRINSIC_ADD_CONDITIONAL_ANNOTATION`` | Adds an annotation index to 
the         |
+   |                                          | 
``__conditional_annotations__``         |
+   |                                          | set.                           
         |
+   
+------------------------------------------+-----------------------------------------+
+   | ``INTRINSIC_MATCH_CLASS_ISINSTANCE``     | Do :func:`isinstance` checks 
for        |
+   |                                          | :ref:`Class patterns 
<class-patterns>`. |
+   
+------------------------------------------+-----------------------------------------+
 
    .. versionadded:: 3.12
 
+   .. versionchanged:: 3.16
+      Added ``INTRINSIC_MATCH_CLASS_ISINSTANCE``.
+
 
 .. opcode:: LOAD_SPECIAL
 
diff --git a/Include/internal/pycore_intrinsics.h 
b/Include/internal/pycore_intrinsics.h
index 447cea91716eca9..e8ddafed9123409 100644
--- a/Include/internal/pycore_intrinsics.h
+++ b/Include/internal/pycore_intrinsics.h
@@ -31,8 +31,9 @@
 #define INTRINSIC_SET_FUNCTION_TYPE_PARAMS       4
 #define INTRINSIC_SET_TYPEPARAM_DEFAULT          5
 #define INTRINSIC_ADD_CONDITIONAL_ANNOTATION     6
+#define INTRINSIC_MATCH_CLASS_ISINSTANCE         7
 
-#define MAX_INTRINSIC_2                          6
+#define MAX_INTRINSIC_2                          7
 
 typedef PyObject *(*intrinsic_func1)(PyThreadState* tstate, PyObject *value);
 typedef PyObject *(*intrinsic_func2)(PyThreadState* tstate, PyObject *value1, 
PyObject *value2);
diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index 36efab518781410..ca0545dba264f2a 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -5752,12 +5752,13 @@ def testfunc(n):
     def test_match_class(self):
         def testfunc(n):
             class A:
+                __match_args__ = ("val",)
                 val = 1
             x = A()
             ret = 0
             for _ in range(n):
                 match x:
-                    case A():
+                    case A(1):
                         ret += x.val
             return ret
 
@@ -5766,7 +5767,7 @@ class A:
         uops = get_opnames(ex)
 
         self.assertIn("_MATCH_CLASS", uops)
-        self.assertEqual(count_ops(ex, "_POP_TOP_NOP"), 4)
+        self.assertEqual(count_ops(ex, "_POP_TOP_NOP"), 5)
 
     def test_dict_update(self):
         def testfunc(n):
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-16-19-22.gh-issue-138912.TKMC9K.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-16-19-22.gh-issue-138912.TKMC9K.rst
new file mode 100644
index 000000000000000..6236c6335ceca3c
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-14-16-19-22.gh-issue-138912.TKMC9K.rst
@@ -0,0 +1,2 @@
+Add fast path for :keyword:`match` class patterns without any sub-patterns
+to improve performance by ~15%.
diff --git a/Python/codegen.c b/Python/codegen.c
index 71ec33724684dc6..7143d9abc4255e8 100644
--- a/Python/codegen.c
+++ b/Python/codegen.c
@@ -6245,6 +6245,13 @@ codegen_pattern_class(compiler *c, pattern_ty p, 
pattern_context *pc)
     if (nattrs) {
         RETURN_IF_ERROR(validate_kwd_attrs(c, kwd_attrs, kwd_patterns));
     }
+    if (nargs + nattrs == 0) {
+        // Fast path if there are no sub-patterns
+        VISIT(c, expr, p->v.MatchClass.cls);
+        ADDOP_I(c, LOC(p), CALL_INTRINSIC_2, INTRINSIC_MATCH_CLASS_ISINSTANCE);
+        RETURN_IF_ERROR(jump_to_fail_pop(c, LOC(p), pc, POP_JUMP_IF_FALSE));
+        return SUCCESS;
+    }
     VISIT(c, expr, p->v.MatchClass.cls);
     PyObject *attr_names = PyTuple_New(nattrs);
     if (attr_names == NULL) {
diff --git a/Python/intrinsics.c b/Python/intrinsics.c
index 52e2dc8a99d8640..dfa81baa654e123 100644
--- a/Python/intrinsics.c
+++ b/Python/intrinsics.c
@@ -287,6 +287,25 @@ add_conditional_annotation(PyThreadState* tstate, PyObject 
*conditional_annotati
     Py_RETURN_NONE;
 }
 
+static PyObject *
+match_class_isinstance(PyThreadState* tstate, PyObject *subject, PyObject 
*type)
+{
+    /* Fast path for class patterns with no sub-patterns, e.g. `case C():`
+       Equivalent to the isinstance check performed by _PyEval_MatchClass,
+       including the same TypeError when the pattern does not refer to a
+       class. */
+    if (!PyType_Check(type)) {
+        _PyErr_SetString(tstate, PyExc_TypeError,
+                         "class pattern must refer to a class");
+        return NULL;
+    }
+    int res = PyObject_IsInstance(subject, type);
+    if (res < 0) {
+        return NULL;
+    }
+    return res ? Py_True : Py_False;
+}
+
 const intrinsic_func2_info
 _PyIntrinsics_BinaryFunctions[] = {
     INTRINSIC_FUNC_ENTRY(INTRINSIC_2_INVALID, no_intrinsic2)
@@ -296,6 +315,7 @@ _PyIntrinsics_BinaryFunctions[] = {
     INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_FUNCTION_TYPE_PARAMS, 
_Py_set_function_type_params)
     INTRINSIC_FUNC_ENTRY(INTRINSIC_SET_TYPEPARAM_DEFAULT, 
_Py_set_typeparam_default)
     INTRINSIC_FUNC_ENTRY(INTRINSIC_ADD_CONDITIONAL_ANNOTATION, 
add_conditional_annotation)
+    INTRINSIC_FUNC_ENTRY(INTRINSIC_MATCH_CLASS_ISINSTANCE, 
match_class_isinstance)
 };
 
 #undef INTRINSIC_FUNC_ENTRY

_______________________________________________
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