https://github.com/python/cpython/commit/75d4839fa9069241ca9c3ee45ad8369ef9777500
commit: 75d4839fa9069241ca9c3ee45ad8369ef9777500
branch: main
author: Marc Mueller <[email protected]>
committer: gpshead <[email protected]>
date: 2026-02-13T21:06:15-08:00
summary:
gh-138912: Improve MATCH_CLASS opcode performance (GH-138915)
Only check for duplicates if there is at least one positional pattern.
With a test case for duplicate keyword attributes.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2025-09-15-13-28-48.gh-issue-138912.61EYbn.rst
M Lib/test/test_syntax.py
M Python/ceval.c
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 19427f2469ec43..e48749626fccad 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -2345,6 +2345,12 @@
Traceback (most recent call last):
SyntaxError: positional patterns follow keyword patterns
+ >>> match ...:
+ ... case Foo(y=1, x=2, y=3):
+ ... ...
+ Traceback (most recent call last):
+ SyntaxError: attribute name repeated in class pattern: y
+
>>> match ...:
... case C(a=b, c, d=e, f, g=h, i, j=k, ...):
... ...
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-15-13-28-48.gh-issue-138912.61EYbn.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-15-13-28-48.gh-issue-138912.61EYbn.rst
new file mode 100644
index 00000000000000..f5d312a289fe21
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-15-13-28-48.gh-issue-138912.61EYbn.rst
@@ -0,0 +1 @@
+Improve :opcode:`MATCH_CLASS` performance by up to 52% in certain cases. Patch
by Marc Mueller.
diff --git a/Python/ceval.c b/Python/ceval.c
index 758b142d7a720e..ab2eef560370f5 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -509,15 +509,18 @@ match_class_attr(PyThreadState *tstate, PyObject
*subject, PyObject *type,
PyObject *name, PyObject *seen)
{
assert(PyUnicode_CheckExact(name));
- assert(PySet_CheckExact(seen));
- if (PySet_Contains(seen, name) || PySet_Add(seen, name)) {
- if (!_PyErr_Occurred(tstate)) {
- // Seen it before!
- _PyErr_Format(tstate, PyExc_TypeError,
- "%s() got multiple sub-patterns for attribute %R",
- ((PyTypeObject*)type)->tp_name, name);
+ // Only check for duplicates if seen is not NULL.
+ if (seen != NULL) {
+ assert(PySet_CheckExact(seen));
+ if (PySet_Contains(seen, name) || PySet_Add(seen, name)) {
+ if (!_PyErr_Occurred(tstate)) {
+ // Seen it before!
+ _PyErr_Format(tstate, PyExc_TypeError,
+ "%s() got multiple sub-patterns for attribute %R",
+ ((PyTypeObject*)type)->tp_name, name);
+ }
+ return NULL;
}
- return NULL;
}
PyObject *attr;
(void)PyObject_GetOptionalAttr(subject, name, &attr);
@@ -540,14 +543,26 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject
*subject, PyObject *type,
if (PyObject_IsInstance(subject, type) <= 0) {
return NULL;
}
+ // Short circuit if there aren't any arguments:
+ Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwargs);
+ Py_ssize_t nattrs = nargs + nkwargs;
+ if (!nattrs) {
+ return PyTuple_New(0);
+ }
// So far so good:
- PyObject *seen = PySet_New(NULL);
- if (seen == NULL) {
- return NULL;
+ PyObject *seen = NULL;
+ // Only check for duplicates if there is at least one positional attribute
+ // and two or more attributes in total. Duplicate keyword attributes are
+ // detected during the compile stage and raise a SyntaxError.
+ if (nargs > 0 && nattrs > 1) {
+ seen = PySet_New(NULL);
+ if (seen == NULL) {
+ return NULL;
+ }
}
- PyObject *attrs = PyList_New(0);
+ PyObject *attrs = PyTuple_New(nattrs);
if (attrs == NULL) {
- Py_DECREF(seen);
+ Py_XDECREF(seen);
return NULL;
}
// NOTE: From this point on, goto fail on failure:
@@ -588,9 +603,8 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject
*subject, PyObject *type,
}
if (match_self) {
// Easy. Copy the subject itself, and move on to kwargs.
- if (PyList_Append(attrs, subject) < 0) {
- goto fail;
- }
+ assert(PyTuple_GET_ITEM(attrs, 0) == NULL);
+ PyTuple_SET_ITEM(attrs, 0, Py_NewRef(subject));
}
else {
for (Py_ssize_t i = 0; i < nargs; i++) {
@@ -606,36 +620,29 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject
*subject, PyObject *type,
if (attr == NULL) {
goto fail;
}
- if (PyList_Append(attrs, attr) < 0) {
- Py_DECREF(attr);
- goto fail;
- }
- Py_DECREF(attr);
+ assert(PyTuple_GET_ITEM(attrs, i) == NULL);
+ PyTuple_SET_ITEM(attrs, i, attr);
}
}
Py_CLEAR(match_args);
}
// Finally, the keyword subpatterns:
- for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(kwargs); i++) {
+ for (Py_ssize_t i = 0; i < nkwargs; i++) {
PyObject *name = PyTuple_GET_ITEM(kwargs, i);
PyObject *attr = match_class_attr(tstate, subject, type, name, seen);
if (attr == NULL) {
goto fail;
}
- if (PyList_Append(attrs, attr) < 0) {
- Py_DECREF(attr);
- goto fail;
- }
- Py_DECREF(attr);
+ assert(PyTuple_GET_ITEM(attrs, nargs + i) == NULL);
+ PyTuple_SET_ITEM(attrs, nargs + i, attr);
}
- Py_SETREF(attrs, PyList_AsTuple(attrs));
- Py_DECREF(seen);
+ Py_XDECREF(seen);
return attrs;
fail:
// We really don't care whether an error was raised or not... that's our
// caller's problem. All we know is that the match failed.
Py_XDECREF(match_args);
- Py_DECREF(seen);
+ Py_XDECREF(seen);
Py_DECREF(attrs);
return NULL;
}
_______________________________________________
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]