From 4db80fe4b067ebf611fd3c6eac3345621b63a9d7 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 15 Oct 2026 14:10:09 +0200 Subject: [PATCH] gh-68656: Pass CO_* constants for inspect from C; deprecate CO_NOFREE The constants were redefined in dis.py, and not perfectly kept in sync: inspect.CO_NOFREE was kept even though the C define went away in gh-26839. - Expose the constants via `_opcode` (as agreed in gh-68656) and its wrapper `opcode`. This removes `CO_NOFREE` from `dis.COMPILER_FLAG_NAMES`. - Re-add, deprecate and document inspect.CO_NOFREE --- Doc/deprecations/pending-removal-in-3.21.rst | 5 +++ Doc/library/inspect.rst | 10 ++++++ Doc/reference/datamodel.rst | 14 ++++---- Doc/whatsnew/3.16.rst | 5 +++ Include/cpython/code.h | 2 ++ Lib/dis.py | 18 ---------- Lib/inspect.py | 28 +++++++-------- Lib/opcode.py | 5 ++- Lib/test/test_inspect/test_inspect.py | 13 +++++++ ...6-10-15-14-33-36.gh-issue-68656.r1YN0i.rst | 1 + Modules/_opcode.c | 34 +++++++++++++++++++ 11 files changed, 96 insertions(+), 39 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-15-14-33-36.gh-issue-68656.r1YN0i.rst diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index c2546b7fc451aed..1f05dd5ed656ee7 100644 --- a/Doc/deprecations/pending-removal-in-3.21.rst +++ b/Doc/deprecations/pending-removal-in-3.21.rst @@ -18,6 +18,11 @@ Pending removal in Python 3.21 * The ``dims`` property of ``ast.Tuple`` will be removed in Python 3.21. Use the ``ast.Tuple.elts`` property instead. +* :mod:`inspect`: + + * :attr:`inspect.CO_NOFREE`, a name for an unused compiler flag, will + be removed in Python 3.21. + * :mod:`struct`: * Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes are now diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 624b13029ec78ba..fcd20366d53e5a1 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -1871,6 +1871,16 @@ which is a bitmap of the following flags: .. versionadded:: 3.14 +.. data:: CO_NOFREE + + Unused; always unset. + + .. versionchanged:: 3.5 + Previously, this flag indicated whether there are free variables + in the code object. + + .. deprecated-removed:: next 3.21 + .. note:: The flags are specific to CPython, and may not be defined in other Python implementations. Furthermore, the flags are an implementation diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index c860a3356628af1..d4aa6a52d5a2791 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1576,13 +1576,15 @@ Special read-only attributes .. index:: pair: object; generator -The following flag bits are defined for :attr:`~codeobject.co_flags`: -bit ``0x04`` is set if +The :attr:`~codeobject.co_flags` attribute uses flag bits listed in +:ref:`inspect-module-co-flags`. +For example, :attr:`~inspect.CO_VARARGS` (bit ``0x04``) is set if the function uses the ``*arguments`` syntax to accept an arbitrary number of -positional arguments; bit ``0x08`` is set if the function uses the -``**keywords`` syntax to accept arbitrary keyword arguments; bit ``0x20`` is set -if the function is a generator. See :ref:`inspect-module-co-flags` for details -on the semantics of each flags that might be present. +positional arguments; :attr:`~inspect.CO_VARKEYWORDS` (bit ``0x08``) +is set if the function uses the ``**keywords`` syntax to accept arbitrary +keyword arguments; +:attr:`~inspect.CO_GENERATOR` (bit ``0x20``) is set if the function +is a generator. Future feature declarations (for example, ``from __future__ import division``) also use bits in :attr:`~codeobject.co_flags` to indicate whether a code object was compiled with a diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index a1d7d1eaae7d526..b01fcd953be243b 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -1029,6 +1029,11 @@ New deprecations 3.9, now issues a deprecation warning on use. This property is slated for removal in 3.21. Use ``ast.Tuple.elts`` instead. +* :mod:`inspect`: + + * :attr:`inspect.CO_NOFREE`, a name for an unused compiler flag, will + be removed in Python 3.21. + * :mod:`struct`: * The ``'F'`` and ``'D'`` type codes, soft-deprecated since Python 3.15, diff --git a/Include/cpython/code.h b/Include/cpython/code.h index 1be47e42ed62bfb..1afee56264e2363 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -119,12 +119,14 @@ typedef struct { struct PyCodeObject _PyCode_DEF(1); /* Masks for co_flags above */ +// Keep in sync with _opcode.c, which exposes these for inspect & dis #define CO_OPTIMIZED 0x0001 #define CO_NEWLOCALS 0x0002 #define CO_VARARGS 0x0004 #define CO_VARKEYWORDS 0x0008 #define CO_NESTED 0x0010 #define CO_GENERATOR 0x0020 +// CO_NOFREE used to be 0x0040 -- don't reuse before inspect.CO_NOFREE is gone /* The CO_COROUTINE flag is set for coroutine functions (defined with ``async def`` keywords) */ diff --git a/Lib/dis.py b/Lib/dis.py index 318729091fa098c..7ef04b3d936ea9d 100644 --- a/Lib/dis.py +++ b/Lib/dis.py @@ -155,24 +155,6 @@ def distb(tb=None, *, file=None, show_caches=False, adaptive=False, show_offsets disassemble(tb.tb_frame.f_code, tb.tb_lasti, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions, show_jit=show_jit) -# The inspect module interrogates this dictionary to build its -# list of CO_* constants. It is also used by pretty_flags to -# turn the co_flags field into a human readable list. -COMPILER_FLAG_NAMES = { - 1: "OPTIMIZED", - 2: "NEWLOCALS", - 4: "VARARGS", - 8: "VARKEYWORDS", - 16: "NESTED", - 32: "GENERATOR", - 64: "NOFREE", - 128: "COROUTINE", - 256: "ITERABLE_COROUTINE", - 512: "ASYNC_GENERATOR", - 0x4000000: "HAS_DOCSTRING", - 0x8000000: "METHOD", -} - def pretty_flags(flags): """Return pretty representation of code flags.""" names = [] diff --git a/Lib/inspect.py b/Lib/inspect.py index bceed35a53abba1..99755ec526030d3 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -46,18 +46,7 @@ "CORO_CREATED", "CORO_RUNNING", "CORO_SUSPENDED", - "CO_ASYNC_GENERATOR", - "CO_COROUTINE", - "CO_GENERATOR", - "CO_ITERABLE_COROUTINE", - "CO_NESTED", - "CO_NEWLOCALS", - "CO_NOFREE", - "CO_OPTIMIZED", - "CO_VARARGS", - "CO_VARKEYWORDS", - "CO_HAS_DOCSTRING", - "CO_METHOD", + # CO_* -- added below "ClassFoundException", "ClosureVars", "EndOfBlock", @@ -169,8 +158,10 @@ # We try to get them from dis to avoid duplication mod_dict = globals() for k, v in dis.COMPILER_FLAG_NAMES.items(): - mod_dict["CO_" + v] = k -del k, v, mod_dict + name = "CO_" + v + mod_dict[name] = k + __all__.append(name) +del k, v, name, mod_dict # See Include/object.h TPFLAGS_IS_ABSTRACT = 1 << 20 @@ -3475,6 +3466,15 @@ def _render_details_for_cli(details): return "\n".join(lines) +def __getattr__(name): + if name == "CO_NOFREE": + # CO_NOFREE: removed from C code in GH-26839 (Python 3.5) + import warnings + warnings._deprecated("inspect.CO_NOFREE", remove=(3, 21)) + return 64 + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + def _main(): """ Logic for inspecting an object given at command line """ import argparse diff --git a/Lib/opcode.py b/Lib/opcode.py index 750d83b2c87af58..f05f748c1cb609d 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -7,7 +7,8 @@ __all__ = ["cmp_op", "stack_effect", "hascompare", "opname", "opmap", "HAVE_ARGUMENT", "EXTENDED_ARG", "hasarg", "hasconst", "hasname", - "hasjump", "hasjrel", "hasjabs", "hasfree", "haslocal", "hasexc"] + "hasjump", "hasjrel", "hasjabs", "hasfree", "haslocal", "hasexc", + "COMPILER_FLAG_NAMES"] import builtins import _opcode @@ -17,6 +18,8 @@ HAVE_ARGUMENT, MIN_INSTRUMENTED_OPCODE) # noqa: F401 EXTENDED_ARG = opmap['EXTENDED_ARG'] +COMPILER_FLAG_NAMES = {n: name for name, n in _opcode.COMPILER_FLAGS.items()} + opname = ['<%r>' % (op,) for op in range(max(opmap.values()) + 1)] for m in (opmap, _specialized_opmap): for op, i in m.items(): diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 3f14a9e66226eaa..9cc0da6a6ef02c9 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -43,6 +43,7 @@ from test.support.os_helper import TESTFN, TESTFN_UNDECODABLE, temp_cwd from test.support.script_helper import assert_python_ok, assert_python_failure, kill_python from test.support import has_subprocess_support +from test.support import warnings_helper from test import support from test.test_inspect import inspect_fodder as mod @@ -6973,5 +6974,17 @@ def f(): self.assertIn(expected, output) +class TestCompilerConstants(unittest.TestCase): + def test_previously_documented_values(self): + # The numeric values are in the docs; we better not change them + self.assertEqual(inspect.CO_VARARGS, 0x04) + self.assertEqual(inspect.CO_VARKEYWORDS, 0x08) + self.assertEqual(inspect.CO_GENERATOR, 0x20) + + @warnings_helper.ignore_warnings(category=DeprecationWarning) + def test_nofree(self): + self.assertEqual(inspect.CO_NOFREE, 0x40) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-10-15-14-33-36.gh-issue-68656.r1YN0i.rst b/Misc/NEWS.d/next/Library/2026-10-15-14-33-36.gh-issue-68656.r1YN0i.rst new file mode 100644 index 000000000000000..a58a357b13656e5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-15-14-33-36.gh-issue-68656.r1YN0i.rst @@ -0,0 +1 @@ +Deprecate :py:attr:`inspect.CO_NOFREE`, a name for an unused compiler flag. diff --git a/Modules/_opcode.c b/Modules/_opcode.c index 2a34559fd1f4378..9fe1549e3b189c4 100644 --- a/Modules/_opcode.c +++ b/Modules/_opcode.c @@ -423,6 +423,40 @@ _opcode_exec(PyObject *m) { if (PyModule_AddIntMacro(m, ENABLE_SPECIALIZATION) < 0) { return -1; } + PyObject *compiler_flag_names = PyDict_New(); + if (compiler_flag_names == NULL) { + return -1; + } + PyObject *val; +# define ADD_CO(NAME) \ + do { \ + val = PyLong_FromLong(CO_ ## NAME); \ + if (val == NULL) { \ + return -1; \ + } \ + if (PyDict_SetItemString(compiler_flag_names, #NAME, val) < 0) { \ + return -1; \ + } \ + Py_DECREF(val); \ + } while(0) \ + ///////////////////////////////////////////////////////////////////////// + ADD_CO(OPTIMIZED); + ADD_CO(NEWLOCALS); + ADD_CO(VARARGS); + ADD_CO(VARKEYWORDS); + ADD_CO(NESTED); + ADD_CO(GENERATOR); + ADD_CO(COROUTINE); + ADD_CO(ITERABLE_COROUTINE); + ADD_CO(ASYNC_GENERATOR); + ADD_CO(HAS_DOCSTRING); + ADD_CO(METHOD); +# undef ADD_CO + if (PyModule_Add(m, "COMPILER_FLAGS", + PyFrozenDict_New(compiler_flag_names)) < 0) { + return -1; + } + Py_DECREF(compiler_flag_names); return 0; }