diff --git a/Doc/library/dis.rst b/Doc/library/dis.rst index 5cc0d68a24acee9..188c382e87baccd 100644 --- a/Doc/library/dis.rst +++ b/Doc/library/dis.rst @@ -1899,28 +1899,34 @@ 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. | - +----------------------------------------+-----------------------------------+ + +----------------------------------------+-----------------------------------------+ + | 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_MATCH_CLASS_ISINSTANCE`` | Do :func:`isinstance` checks for | + | | :ref:`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 59a7b16073f886c..cea7f3c6363cae5 100644 --- a/Include/internal/pycore_intrinsics.h +++ b/Include/internal/pycore_intrinsics.h @@ -30,8 +30,9 @@ #define INTRINSIC_TYPEVAR_WITH_CONSTRAINTS 3 #define INTRINSIC_SET_FUNCTION_TYPE_PARAMS 4 #define INTRINSIC_SET_TYPEPARAM_DEFAULT 5 +#define INTRINSIC_MATCH_CLASS_ISINSTANCE 6 -#define MAX_INTRINSIC_2 5 +#define MAX_INTRINSIC_2 6 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 25b2c393e6773de..a291218934f1b6c 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -5721,12 +5721,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 @@ -5735,7 +5736,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 81fecd09a2060d1..79dac1046eb1d44 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -6179,6 +6179,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 f081f33cc83b88c..63410d8dbe584ad 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -270,6 +270,25 @@ make_typevar_with_constraints(PyThreadState* Py_UNUSED(ignored), PyObject *name, return _Py_make_typevar(name, NULL, evaluate_constraints); } +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) @@ -278,6 +297,7 @@ _PyIntrinsics_BinaryFunctions[] = { INTRINSIC_FUNC_ENTRY(INTRINSIC_TYPEVAR_WITH_CONSTRAINTS, make_typevar_with_constraints) 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_MATCH_CLASS_ISINSTANCE, match_class_isinstance) }; #undef INTRINSIC_FUNC_ENTRY