diff --git a/Doc/conf.py b/Doc/conf.py index 0fb8b5cd800cfb0..8791aba435f49ea 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -469,6 +469,10 @@ # https://github.com/sphinx-doc/sphinx/issues/12359 epub_use_index = False +# The default depth of 3 lists over a thousand entries, which makes the +# table of contents hard to navigate in e-book readers +epub_tocdepth = 2 + # translation tag # --------------- 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 `. | + +------------------------------------------+-----------------------------------------+ .. 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/lazy_imports_all_exclude.txt b/Lib/test/lazy_imports_all_exclude.txt index 2680d3b1e4357b7..45796d1db9f0a58 100644 --- a/Lib/test/lazy_imports_all_exclude.txt +++ b/Lib/test/lazy_imports_all_exclude.txt @@ -35,6 +35,5 @@ test_pyrepl test_subprocess test_symtable test_tools -test_trace test_type_annotations test_unittest 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/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py index 9147e788d7a81f2..5026c9670d81d23 100644 --- a/Lib/test/test_lazy_import/__init__.py +++ b/Lib/test/test_lazy_import/__init__.py @@ -724,10 +724,17 @@ def test_non_package_lazily_imported(self): assert_python_ok("-c", code) def test_non_package_lazily_imported_as(self): - """Doing a dotted lazy import as still works""" + """A dotted lazy import as raises when the name is not a module.""" + # gh-157757: the eager statement raises, so the lazy one raises too. code = textwrap.dedent(""" lazy import math.pi as pi - pi + + try: + pi + except ModuleNotFoundError: + pass + else: + raise AssertionError("ModuleNotFoundError was not raised") """) assert_python_ok("-c", code) @@ -1170,6 +1177,22 @@ def test_accessing_one_name_leaves_others_as_proxies(self): self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}") self.assertIn("OK", result.stdout) + def test_accessing_one_name_imports_only_its_submodule(self): + """Accessing one name should not import the other names' submodules.""" + code = textwrap.dedent(""" + import sys + + lazy from test.test_lazy_import.data.pkg import b, bar, broken + + # Importing bar prints, and importing broken raises. + b.foo() + + assert "test.test_lazy_import.data.pkg.bar" not in sys.modules + assert "test.test_lazy_import.data.pkg.broken" not in sys.modules + """) + rc, out, err = assert_python_ok("-c", code) + self.assertEqual(out, b"") + def test_all_names_reified_after_all_accessed(self): """All names should be reified after each is accessed.""" code = textwrap.dedent(""" @@ -2209,6 +2232,122 @@ def test_import_after_variable_wins(self): ] self.assertIs(module_same_name_var_order2.bar, bar_mod) + def test_lazy_import_as_wins_over_variable(self): + """A dotted lazy import as imports the submodule the variable hides.""" + # gh-157757: importing pkg.b rebinds pkg.b from the variable to the + # module, eagerly and lazily alike. + code = textwrap.dedent(""" + import sys + import test.test_lazy_import.data.pkg as pkg + pkg.b = "hides the b submodule" + + lazy import test.test_lazy_import.data.pkg.b as b + lazy import test.test_lazy_import.data.metasyntactic.foo.bar as bar + + assert b is sys.modules["test.test_lazy_import.data.pkg.b"], b + assert bar is sys.modules[ + "test.test_lazy_import.data.metasyntactic.foo.bar"], bar + """) + assert_python_ok("-c", code) + + def test_dotted_as_of_loaded_module(self): + """A dotted lazy import as binds the module, not a same-named attribute.""" + # importlib.metadata is already loaded and has a `metadata` attribute. + code = textwrap.dedent(""" + import importlib.metadata + import importlib.metadata as eager + + lazy import importlib.metadata as lazily + + assert lazily is eager, lazily + """) + assert_python_ok("-c", code) + + def test_dotted_as_replays_lookups_on_custom_placeholder(self): + """A dotted lazy import as looks up its names on what the hook returned.""" + code = textwrap.dedent(""" + import builtins + import xml.dom + + # In a list, so the hook reading it does not resolve it. + placeholder = [__lazy_import__("xml")] + default = builtins.__lazy_import__ + builtins.__lazy_import__ = lambda *args: placeholder[0] + lazy import fake.dom as dom + builtins.__lazy_import__ = default + + assert dom is xml.dom, dom + """) + assert_python_ok("-c", code) + + def test_empty_fromlist_placeholder_matches_no_fromlist(self): + """An empty fromlist behaves like None.""" + code = textwrap.dedent(""" + expected = "" + # In lists, so reading them does not resolve them. + for fromlist in (None, ()): + same = [__lazy_import__("xml.dom", fromlist=fromlist)] + assert repr(same[0]) == expected, (fromlist, repr(same[0])) + bare = [__lazy_import__("xml.dom")] + assert repr(bare[0]) == expected, repr(bare[0]) + """) + assert_python_ok("-c", code) + + def test_empty_fromlist_preserved_for_custom_import(self): + code = textwrap.dedent(""" + import builtins + import types + + value = object() + module = types.SimpleNamespace(dom=value) + placeholder = [__lazy_import__("xml.dom", fromlist=())] + default_import = builtins.__import__ + default_lazy_import = builtins.__lazy_import__ + calls = [] + + def import_hook(name, globals, locals, fromlist, level): + assert name == "xml.dom", name + assert fromlist == (), fromlist + calls.append(fromlist) + return module + + builtins.__import__ = import_hook + assert placeholder[0].resolve() is module + builtins.__lazy_import__ = lambda *args: placeholder[0] + lazy import fake.dom as dom + assert dom is value + builtins.__import__ = default_import + builtins.__lazy_import__ = default_lazy_import + + assert calls == [(), ()], calls + """) + assert_python_ok("-c", code) + + def test_dotted_as_replays_lookups_on_dotted_placeholder(self): + """A dotted lazy import as replays its names on the hook's package.""" + # importlib.metadata has a `metadata` attribute of its own, which the + # placeholder for importlib must not answer with. + for target in ("xml.dom", "importlib.metadata"): + with self.subTest(target=target): + leaf = target.rpartition(".")[2] + code = textwrap.dedent(f""" + import builtins + import sys + import {target} + + # In a list, so the hook reading it does not resolve it. + placeholder = [__lazy_import__("{target}", fromlist=())] + default = builtins.__lazy_import__ + builtins.__lazy_import__ = lambda *args: placeholder[0] + lazy import fake.{leaf} as {leaf} + builtins.__lazy_import__ = default + + name = repr(globals()["{leaf}"]) + assert name == "", name + assert {leaf} is sys.modules["{target}"], {leaf} + """) + assert_python_ok("-c", code) + class DeletedModuleReimportTests(unittest.TestCase): """Tests for reimporting after module deletion from sys.modules.""" diff --git a/Lib/test/test_lazy_import/data/pkg/broken.py b/Lib/test/test_lazy_import/data/pkg/broken.py new file mode 100644 index 000000000000000..6499aa9e15030a6 --- /dev/null +++ b/Lib/test/test_lazy_import/data/pkg/broken.py @@ -0,0 +1,2 @@ +# Submodule that raises an error during import +raise ValueError("This module always fails to import") 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/Misc/NEWS.d/next/Core_and_Builtins/2026-09-18-15-20-00.gh-issue-157757.Kq3Lm7.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-18-15-20-00.gh-issue-157757.Kq3Lm7.rst new file mode 100644 index 000000000000000..bb5377a53cab566 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-18-15-20-00.gh-issue-157757.Kq3Lm7.rst @@ -0,0 +1,3 @@ +Fix a lazy ``import a.b as c`` reading ``b`` off ``a`` instead of importing +the module ``a.b``. It now binds the submodule, and raises +:exc:`ModuleNotFoundError` when no module backs the name. diff --git a/Misc/NEWS.d/next/Documentation/2026-09-24-10-10-15.gh-issue-57491.UA-2uN.rst b/Misc/NEWS.d/next/Documentation/2026-09-24-10-10-15.gh-issue-57491.UA-2uN.rst new file mode 100644 index 000000000000000..c7c8057776bec4a --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2026-09-24-10-10-15.gh-issue-57491.UA-2uN.rst @@ -0,0 +1,3 @@ +Reduce the depth of the table of contents in the EPUB documentation from +three levels to two, making it much shorter and easier to navigate in e-book +readers. diff --git a/Objects/lazyimportobject.c b/Objects/lazyimportobject.c index 8f7f3f98c291289..e008c076352ebbe 100644 --- a/Objects/lazyimportobject.c +++ b/Objects/lazyimportobject.c @@ -14,8 +14,8 @@ PyObject * _PyLazyImport_New(_PyInterpreterFrame *frame, PyObject *builtins, PyObject *name, PyObject *fromlist) { PyLazyImportObject *m; - if (!name || !PyUnicode_Check(name)) { - PyErr_SetString(PyExc_TypeError, "expected str for name"); + if (!name || !(PyUnicode_Check(name) || PyLazyImport_CheckExact(name))) { + PyErr_SetString(PyExc_TypeError, "expected str or lazy_import for name"); return NULL; } if (fromlist == Py_None || fromlist == NULL) { @@ -104,16 +104,45 @@ lazy_import_getattro(PyObject *op, PyObject *name) return value; } +// The dotted name of the object that resolving the placeholder returns. static PyObject * -lazy_import_name(PyLazyImportObject *m) +lazy_import_path(PyLazyImportObject *m) { - if (m->lz_attr != NULL) { - if (PyUnicode_Check(m->lz_attr)) { - return PyUnicode_FromFormat("%U.%U", m->lz_from, m->lz_attr); - } - else { - return PyUnicode_FromFormat("%U...", m->lz_from); + if (PyLazyImport_CheckExact(m->lz_from)) { + PyObject *base = lazy_import_path((PyLazyImportObject *)m->lz_from); + if (base == NULL) { + return NULL; } + PyObject *res = PyUnicode_FromFormat("%U.%U", base, m->lz_attr); + Py_DECREF(base); + return res; + } + if (m->lz_attr != NULL && + (!PyTuple_Check(m->lz_attr) || PyTuple_GET_SIZE(m->lz_attr) > 0)) { + return Py_NewRef(m->lz_from); + } + // __import__("a.b") returns the top-level package `a`. + Py_ssize_t dot = PyUnicode_FindChar( + m->lz_from, '.', 0, PyUnicode_GET_LENGTH(m->lz_from), 1 + ); + if (dot == -2) { + return NULL; + } + if (dot < 0) { + return Py_NewRef(m->lz_from); + } + return PyUnicode_Substring(m->lz_from, 0, dot); +} + +static PyObject * +lazy_import_name(PyLazyImportObject *m) +{ + if (PyLazyImport_CheckExact(m->lz_from)) { + return lazy_import_path(m); + } + if (m->lz_attr != NULL && + (!PyTuple_Check(m->lz_attr) || PyTuple_GET_SIZE(m->lz_attr) > 0)) { + return PyUnicode_FromFormat("%U...", m->lz_from); } return Py_NewRef(m->lz_from); } diff --git a/Python/ceval.c b/Python/ceval.c index 8cf02651d9a408f..c1fbcb0611384c5 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3333,7 +3333,13 @@ _PyEval_LazyImportFrom(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObje assert(PyUnicode_Check(name)); PyObject *ret; PyLazyImportObject *d = (PyLazyImportObject *)v; - PyObject *mod = PyImport_GetModule(d->lz_from); + PyObject *mod = NULL; + // Only `from a import b` can take b off an already imported a; + // `import a.b as c` has to import a.b first. + if (d->lz_attr != NULL && PyTuple_Check(d->lz_attr) && + PyTuple_GET_SIZE(d->lz_attr) > 0) { + mod = PyImport_GetModule(d->lz_from); + } if (mod != NULL) { // Check if the module already has the attribute, if so, resolve it // eagerly. @@ -3353,34 +3359,7 @@ _PyEval_LazyImportFrom(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObje Py_DECREF(mod); } - if (d->lz_attr != NULL) { - if (PyUnicode_Check(d->lz_attr)) { - PyObject *from = PyUnicode_FromFormat( - "%U.%U", d->lz_from, d->lz_attr); - if (from == NULL) { - return NULL; - } - ret = _PyLazyImport_New(frame, d->lz_builtins, from, name); - Py_DECREF(from); - return ret; - } - } - else { - Py_ssize_t dot = PyUnicode_FindChar( - d->lz_from, '.', 0, PyUnicode_GET_LENGTH(d->lz_from), 1 - ); - if (dot >= 0) { - PyObject *from = PyUnicode_Substring(d->lz_from, 0, dot); - if (from == NULL) { - return NULL; - } - ret = _PyLazyImport_New(frame, d->lz_builtins, from, name); - Py_DECREF(from); - return ret; - } - } - ret = _PyLazyImport_New(frame, d->lz_builtins, d->lz_from, name); - return ret; + return _PyLazyImport_New(frame, d->lz_builtins, v, name); } #define CANNOT_CATCH_MSG "catching classes that do not inherit from "\ 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/import.c b/Python/import.c index 037f15d4ca2bafa..2fa63dd01833a64 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3898,6 +3898,25 @@ _PyImport_ResolveName(PyThreadState *tstate, PyObject *name, return resolve_name(tstate, name, globals, level); } +// Look up, in order, the attributes recorded from the root placeholder to lz +// on the module the root's import returned. +static PyObject * +lazy_import_replay_from(PyThreadState *tstate, PyObject *mod, + PyLazyImportObject *lz) +{ + if (!PyLazyImport_CheckExact(lz->lz_from)) { + return Py_NewRef(mod); + } + PyObject *from = lazy_import_replay_from( + tstate, mod, (PyLazyImportObject *)lz->lz_from); + if (from == NULL) { + return NULL; + } + PyObject *obj = _PyEval_ImportFrom(tstate, from, lz->lz_attr); + Py_DECREF(from); + return obj; +} + PyObject * _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) { @@ -3910,6 +3929,13 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) PyLazyImportObject *lz = (PyLazyImportObject *)lazy_import; PyInterpreterState *interp = tstate->interp; + // Walk back to the placeholder IMPORT_NAME left, and the first lookup on it. + PyLazyImportObject *root = lz, *first = NULL; + while (PyLazyImport_CheckExact(root->lz_from)) { + first = root; + root = (PyLazyImportObject *)root->lz_from; + } + // Acquire the global import lock to serialize reification _PyImport_AcquireLock(interp); @@ -3946,7 +3972,7 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) return NULL; } PyErr_SetImportErrorSubclass(PyExc_ImportCycleError, errmsg, - lz->lz_from, NULL); + root->lz_from, NULL); Py_DECREF(errmsg); Py_DECREF(name); _PyImport_ReleaseLock(interp); @@ -3956,24 +3982,20 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) goto error; } - if (lz->lz_attr != NULL) { - if (PyUnicode_Check(lz->lz_attr)) { - fromlist = PyTuple_New(1); - if (fromlist == NULL) { - goto error; - } - Py_INCREF(lz->lz_attr); - PyTuple_SET_ITEM(fromlist, 0, lz->lz_attr); - } - else { - Py_INCREF(lz->lz_attr); - fromlist = lz->lz_attr; + if (root->lz_attr != NULL) { + // `from a import b, c`: import only the name being resolved. + // Keep an empty tuple intact for custom __import__ hooks. + fromlist = first && PyTuple_GET_SIZE(root->lz_attr) > 0 + ? PyTuple_Pack(1, first->lz_attr) + : Py_NewRef(root->lz_attr); + if (fromlist == NULL) { + goto error; } } PyObject *globals = PyEval_GetGlobals(); - if (PyMapping_GetOptionalItem(lz->lz_builtins, &_Py_ID(__import__), + if (PyMapping_GetOptionalItem(root->lz_builtins, &_Py_ID(__import__), &import_func) < 0) { goto error; } @@ -3983,19 +4005,17 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) } obj = _PyEval_ImportNameWithImport( tstate, import_func, globals, globals, - lz->lz_from, fromlist, _PyLong_GetZero() + root->lz_from, fromlist, _PyLong_GetZero() ); if (obj == NULL) { goto error; } - if (lz->lz_attr != NULL && PyUnicode_Check(lz->lz_attr)) { - PyObject *from = obj; - obj = _PyEval_ImportFrom(tstate, from, lz->lz_attr); - Py_DECREF(from); - if (obj == NULL) { - goto error; - } + PyObject *from = obj; + obj = lazy_import_replay_from(tstate, from, lz); + Py_DECREF(from); + if (obj == NULL) { + goto error; } assert(!PyLazyImport_CheckExact(obj)); 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