]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-134690: Removed deprecated `codetype.co_lnotab` (#134691)
authorsobolevn <mail@sobolevn.me>
Sat, 25 Apr 2026 16:13:48 +0000 (19:13 +0300)
committerGitHub <noreply@github.com>
Sat, 25 Apr 2026 16:13:48 +0000 (19:13 +0300)
15 files changed:
Doc/deprecations/pending-removal-in-3.15.rst
Doc/deprecations/pending-removal-in-future.rst
Doc/library/dis.rst
Doc/library/inspect.rst
Doc/reference/datamodel.rst
Doc/whatsnew/3.10.rst
Doc/whatsnew/3.12.rst
Doc/whatsnew/3.15.rst
Doc/whatsnew/3.6.rst
InternalDocs/code_objects.md
Lib/inspect.py
Lib/test/test_code.py
Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-10-03-18.gh-issue-134690.mUMT16.rst [new file with mode: 0644]
Objects/codeobject.c
Objects/lnotab_notes.txt [deleted file]

index e7f27f73664df39c84c648861c831c667ee904ca..1d9a3095813a6de6edd0712b6dbc4f6192fbba5c 100644 (file)
@@ -60,7 +60,7 @@ Pending removal in Python 3.15
 
 * :mod:`types`:
 
-  * :class:`types.CodeType`: Accessing :attr:`~codeobject.co_lnotab` was
+  * :class:`types.CodeType`: Accessing :attr:`!codeobject.co_lnotab` was
     deprecated in :pep:`626`
     since 3.10 and was planned to be removed in 3.12,
     but it only got a proper :exc:`DeprecationWarning` in 3.12.
index e8306b8efee1c8115feb4b0b4b89191921b6a87b..74f98d33a4b61f1de46da7c7621b348f331e66fa 100644 (file)
@@ -47,7 +47,7 @@ although there is currently no date scheduled for their removal.
 
 * :mod:`codecs`: use :func:`open` instead of :func:`codecs.open`. (:gh:`133038`)
 
-* :attr:`codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method
+* :attr:`!codeobject.co_lnotab`: use the :meth:`codeobject.co_lines` method
   instead.
 
 * :mod:`datetime`:
index 1f7014e9cd426f065d8aa3d404d2a550f18c4d16..3e7ae509fedcea6bb5475d59ef7c6d028533d4f2 100644 (file)
@@ -400,7 +400,7 @@ operation is being performed, so the intermediate analysis object isn't useful:
 
    .. versionchanged:: 3.10
       The :pep:`626` :meth:`~codeobject.co_lines` method is used instead of the
-      :attr:`~codeobject.co_firstlineno` and :attr:`~codeobject.co_lnotab`
+      :attr:`~codeobject.co_firstlineno` and :attr:`!codeobject.co_lnotab`
       attributes of the :ref:`code object <code-objects>`.
 
    .. versionchanged:: 3.13
index ff893a4513926a02ec4bf0887a7a5aba11fe19e8..e23449886a38f15a7c5dcc6a313afb2a72e651f8 100644 (file)
@@ -195,10 +195,6 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
 |                 |                   | read more :ref:`here      |
 |                 |                   | <inspect-module-co-flags>`|
 +-----------------+-------------------+---------------------------+
-|                 | co_lnotab         | encoded mapping of line   |
-|                 |                   | numbers to bytecode       |
-|                 |                   | indices                   |
-+-----------------+-------------------+---------------------------+
 |                 | co_freevars       | tuple of names of free    |
 |                 |                   | variables (referenced via |
 |                 |                   | a function's closure)     |
index 2089984404cec6451a5e46dbae2adb6244552d7a..aef5bbe151cfebad6e95bd2117df61b6ea7795b2 100644 (file)
@@ -1476,7 +1476,6 @@ indirectly) to mutable objects.
    single: co_filename (code object attribute)
    single: co_firstlineno (code object attribute)
    single: co_flags (code object attribute)
-   single: co_lnotab (code object attribute)
    single: co_name (code object attribute)
    single: co_names (code object attribute)
    single: co_nlocals (code object attribute)
@@ -1549,14 +1548,6 @@ Special read-only attributes
    * - .. attribute:: codeobject.co_firstlineno
      - The line number of the first line of the function
 
-   * - .. attribute:: codeobject.co_lnotab
-     - A string encoding the mapping from :term:`bytecode` offsets to line
-       numbers. For details, see the source code of the interpreter.
-
-       .. deprecated:: 3.12
-          This attribute of code objects is deprecated, and may be removed in
-          Python 3.15.
-
    * - .. attribute:: codeobject.co_stacksize
      - The required stack size of the code object
 
index 4b092b1395953040276ef888ef6a7fdc5d797566..8a78dbd90382ed7f8f811be2714a9aa0f6f882ee 100644 (file)
@@ -402,7 +402,7 @@ Tracing events, with the correct line number, are generated for all lines of cod
 The :attr:`~frame.f_lineno` attribute of frame objects will always contain the
 expected line number.
 
-The :attr:`~codeobject.co_lnotab` attribute of
+The :attr:`!codeobject.co_lnotab` attribute of
 :ref:`code objects <code-objects>` is deprecated and
 will be removed in 3.12.
 Code that needs to convert from offset to line number should use the new
index 221956f3dd38195d55fef9cec861551560952c4a..df6cc98eaf1c90a1b72c02b113ea6e30d5ba8595 100644 (file)
@@ -1347,7 +1347,7 @@ Deprecated
   ``int``, convert to int explicitly: ``~int(x)``. (Contributed by Tim Hoffmann
   in :gh:`103487`.)
 
-* Accessing :attr:`~codeobject.co_lnotab` on code objects was deprecated in
+* Accessing :attr:`!codeobject.co_lnotab` on code objects was deprecated in
   Python 3.10 via :pep:`626`,
   but it only got a proper :exc:`DeprecationWarning` in 3.12.
   May be removed in 3.15.
index 9ccd63bd8795f901ba86370556cd817bff409927..8f792800fa64d9af64f76b8b4c0d10de48a57b05 100644 (file)
@@ -1658,6 +1658,14 @@ threading
   (Contributed by Bénédikt Tran in :gh:`134087`.)
 
 
+types
+-----
+
+* Removed deprecated in :pep:`626` since Python 3.12
+  :attr:`!codeobject.co_lnotab` from :class:`types.CodeType`.
+  (Contributed by Nikita Sobolev in :gh:`134690`.)
+
+
 typing
 ------
 
index 9eafc09dbee5f43cb621e79ba1fa5ef9c715280e..bdd35d39e361949b2bfe257e49708785696154fd 100644 (file)
@@ -2173,7 +2173,7 @@ Changes in the Python API
 * :c:func:`PyErr_SetImportError` now sets :exc:`TypeError` when its **msg**
   argument is not set. Previously only ``NULL`` was returned.
 
-* The format of the :attr:`~codeobject.co_lnotab` attribute of code objects
+* The format of the :attr:`!codeobject.co_lnotab` attribute of code objects
   changed to support
   a negative line number delta. By default, Python does not emit bytecode with
   a negative line number delta. Functions using :attr:`frame.f_lineno`,
index a91a7043c1b8d4148f3ab3f09667c0d897ca67ea..cccbe71588622cfdd8b8e9ad236e98e6f287e326 100644 (file)
@@ -70,14 +70,6 @@ The `co_linetable` bytes object of code objects contains a compact
 representation of the source code positions of instructions, which are
 returned by the `co_positions()` iterator.
 
-> [!NOTE]
-> `co_linetable` is not to be confused with `co_lnotab`.
-> For backwards compatibility, `co_lnotab` exposes the format
-> as it existed in Python 3.10 and lower: this older format
-> stores only the start line for each instruction.
-> It is lazily created from `co_linetable` when accessed.
-> See [`Objects/lnotab_notes.txt`](../Objects/lnotab_notes.txt) for more details.
-
 `co_linetable` consists of a sequence of location entries.
 Each entry starts with a byte with the most significant bit set, followed by
 zero or more bytes with the most significant bit unset.
index dfc5503dee536e9b8e5384da987c243a9b3a8062..d3af61b26e280a3a5435c395c69c6c3c231dfeed 100644 (file)
@@ -416,7 +416,6 @@ def iscode(object):
         co_freevars         tuple of names of free variables
         co_posonlyargcount  number of positional only arguments
         co_kwonlyargcount   number of keyword only arguments (not including ** arg)
-        co_lnotab           encoded mapping of line numbers to bytecode indices
         co_name             name with which this code object was defined
         co_names            tuple of names other than arguments and function locals
         co_nlocals          number of local variables
@@ -1634,7 +1633,6 @@ def getframeinfo(frame, context=1):
 
 def getlineno(frame):
     """Get the line number from a frame object, allowing for optimization."""
-    # FrameType.f_lineno is now a descriptor that grovels co_lnotab
     return frame.f_lineno
 
 _FrameInfo = namedtuple('_FrameInfo', ('frame',) + Traceback._fields)
index fac7e9148f1502c609bfc21cc035a6e1e4d84705..5e802a929b14b88fc51c11f921ecf1a8e0be7a2d 100644 (file)
@@ -424,13 +424,6 @@ class CodeTest(unittest.TestCase):
         new_code = code = func.__code__.replace(co_linetable=b'')
         self.assertEqual(list(new_code.co_lines()), [])
 
-    def test_co_lnotab_is_deprecated(self):  # TODO: remove in 3.14
-        def func():
-            pass
-
-        with self.assertWarns(DeprecationWarning):
-            func.__code__.co_lnotab
-
     @unittest.skipIf(_testinternalcapi is None, '_testinternalcapi is missing')
     def test_returns_only_none(self):
         value = True
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-10-03-18.gh-issue-134690.mUMT16.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-26-10-03-18.gh-issue-134690.mUMT16.rst
new file mode 100644 (file)
index 0000000..d26fa59
--- /dev/null
@@ -0,0 +1,2 @@
+Removed deprecated in :pep:`626` since Python 3.12
+:attr:`!codeobject.co_lnotab` from :class:`types.CodeType`.
index 2c3d6dc4b0feedb56da32c8723b3eb0017e3d65a..50ebe657a0eea67323d09aa86565a597ecd8340f 100644 (file)
@@ -1296,77 +1296,6 @@ _PyLineTable_NextAddressRange(PyCodeAddressRange *range)
     return 1;
 }
 
-static int
-emit_pair(PyObject **bytes, int *offset, int a, int b)
-{
-    Py_ssize_t len = PyBytes_GET_SIZE(*bytes);
-    if (*offset + 2 >= len) {
-        if (_PyBytes_Resize(bytes, len * 2) < 0)
-            return 0;
-    }
-    unsigned char *lnotab = (unsigned char *) PyBytes_AS_STRING(*bytes);
-    lnotab += *offset;
-    *lnotab++ = a;
-    *lnotab++ = b;
-    *offset += 2;
-    return 1;
-}
-
-static int
-emit_delta(PyObject **bytes, int bdelta, int ldelta, int *offset)
-{
-    while (bdelta > 255) {
-        if (!emit_pair(bytes, offset, 255, 0)) {
-            return 0;
-        }
-        bdelta -= 255;
-    }
-    while (ldelta > 127) {
-        if (!emit_pair(bytes, offset, bdelta, 127)) {
-            return 0;
-        }
-        bdelta = 0;
-        ldelta -= 127;
-    }
-    while (ldelta < -128) {
-        if (!emit_pair(bytes, offset, bdelta, -128)) {
-            return 0;
-        }
-        bdelta = 0;
-        ldelta += 128;
-    }
-    return emit_pair(bytes, offset, bdelta, ldelta);
-}
-
-static PyObject *
-decode_linetable(PyCodeObject *code)
-{
-    PyCodeAddressRange bounds;
-    PyObject *bytes;
-    int table_offset = 0;
-    int code_offset = 0;
-    int line = code->co_firstlineno;
-    bytes = PyBytes_FromStringAndSize(NULL, 64);
-    if (bytes == NULL) {
-        return NULL;
-    }
-    _PyCode_InitAddressRange(code, &bounds);
-    while (_PyLineTable_NextAddressRange(&bounds)) {
-        if (bounds.opaque.computed_line != line) {
-            int bdelta = bounds.ar_start - code_offset;
-            int ldelta = bounds.opaque.computed_line - line;
-            if (!emit_delta(&bytes, bdelta, ldelta, &table_offset)) {
-                Py_DECREF(bytes);
-                return NULL;
-            }
-            code_offset = bounds.ar_start;
-            line = bounds.opaque.computed_line;
-        }
-    }
-    _PyBytes_Resize(&bytes, table_offset);
-    return bytes;
-}
-
 
 typedef struct {
     PyObject_HEAD
@@ -2739,18 +2668,6 @@ static PyMemberDef code_memberlist[] = {
 };
 
 
-static PyObject *
-code_getlnotab(PyObject *self, void *closure)
-{
-    PyCodeObject *code = _PyCodeObject_CAST(self);
-    if (PyErr_WarnEx(PyExc_DeprecationWarning,
-                     "co_lnotab is deprecated, use co_lines instead.",
-                     1) < 0) {
-        return NULL;
-    }
-    return decode_linetable(code);
-}
-
 static PyObject *
 code_getvarnames(PyObject *self, void *closure)
 {
@@ -2788,7 +2705,6 @@ code_getcode(PyObject *self, void *closure)
 }
 
 static PyGetSetDef code_getsetlist[] = {
-    {"co_lnotab",         code_getlnotab,       NULL, NULL},
     {"_co_code_adaptive", code_getcodeadaptive, NULL, NULL},
     // The following old names are kept for backward compatibility.
     {"co_varnames",       code_getvarnames,     NULL, NULL},
diff --git a/Objects/lnotab_notes.txt b/Objects/lnotab_notes.txt
deleted file mode 100644 (file)
index 335e441..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-Description of the internal format of the line number table in Python 3.10
-and earlier.
-
-(For 3.11 onwards, see InternalDocs/code_objects.md)
-
-Conceptually, the line number table consists of a sequence of triples:
-    start-offset (inclusive), end-offset (exclusive), line-number.
-
-Note that not all byte codes have a line number so we need handle `None` for the line-number.
-
-However, storing the above sequence directly would be very inefficient as we would need 12 bytes per entry.
-
-First, note that the end of one entry is the same as the start of the next, so we can overlap entries.
-Second, we don't really need arbitrary access to the sequence, so we can store deltas.
-
-We just need to store (end - start, line delta) pairs. The start offset of the first entry is always zero.
-
-Third, most deltas are small, so we can use a single byte for each value, as long we allow several entries for the same line.
-
-Consider the following table
-     Start    End     Line
-      0       6       1
-      6       50      2
-      50      350     7
-      350     360     No line number
-      360     376     8
-      376     380     208
-
-Stripping the redundant ends gives:
-
-   End-Start  Line-delta
-      6         +1
-      44        +1
-      300       +5
-      10        No line number
-      16        +1
-      4         +200
-
-
-Note that the end - start value is always positive.
-
-Finally, in order to fit into a single byte we need to convert start deltas to the range 0 <= delta <= 254,
-and line deltas to the range -127  <= delta <= 127.
-A line delta of -128 is used to indicate no line number.
-Also note that a delta of zero indicates that there are no bytecodes in the given range,
-which means we can use an invalid line number for that range.
-
-Final form:
-
-   Start delta   Line delta
-    6               +1
-    44              +1
-    254             +5
-    46              0
-    10              -128 (No line number, treated as a delta of zero)
-    16              +1
-    0               +127 (line 135, but the range is empty as no bytecodes are at line 135)
-    4               +73
-
-Iterating over the table.
--------------------------
-
-For the `co_lines` method we want to emit the full form, omitting the (350, 360, No line number) and empty entries.
-
-The code is as follows:
-
-def co_lines(code):
-    line = code.co_firstlineno
-    end = 0
-    table_iter = iter(code.internal_line_table):
-    for sdelta, ldelta in table_iter:
-        if ldelta == 0: # No change to line number, just accumulate changes to end
-            end += sdelta
-            continue
-        start = end
-        end = start + sdelta
-        if ldelta == -128: # No valid line number -- skip entry
-            continue
-        line += ldelta
-        if end == start: # Empty range, omit.
-            continue
-        yield start, end, line
-
-
-
-
-The historical co_lnotab format
--------------------------------
-
-prior to 3.10 code objects stored a field named co_lnotab.
-This was an array of unsigned bytes disguised as a Python bytes object.
-
-The old co_lnotab did not account for the presence of bytecodes without a line number,
-nor was it well suited to tracing as a number of workarounds were required.
-
-The old format can still be accessed via `code.co_lnotab`, which is lazily computed from the new format.
-
-Below is the description of the old co_lnotab format:
-
-
-The array is conceptually a compressed list of
-    (bytecode offset increment, line number increment)
-pairs.  The details are important and delicate, best illustrated by example:
-
-    byte code offset    source code line number
-        0                   1
-        6                   2
-       50                   7
-      350                 207
-      361                 208
-
-Instead of storing these numbers literally, we compress the list by storing only
-the difference from one row to the next.  Conceptually, the stored list might
-look like:
-
-    0, 1,  6, 1,  44, 5,  300, 200,  11, 1
-
-The above doesn't really work, but it's a start. An unsigned byte (byte code
-offset) can't hold negative values, or values larger than 255, a signed byte
-(line number) can't hold values larger than 127 or less than -128, and the
-above example contains two such values.  (Note that before 3.6, line number
-was also encoded by an unsigned byte.)  So we make two tweaks:
-
- (a) there's a deep assumption that byte code offsets increase monotonically,
- and
- (b) if byte code offset jumps by more than 255 from one row to the next, or if
- source code line number jumps by more than 127 or less than -128 from one row
- to the next, more than one pair is written to the table. In case #b,
- there's no way to know from looking at the table later how many were written.
- That's the delicate part.  A user of co_lnotab desiring to find the source
- line number corresponding to a bytecode address A should do something like
- this:
-
-    lineno = addr = 0
-    for addr_incr, line_incr in co_lnotab:
-        addr += addr_incr
-        if addr > A:
-            return lineno
-        if line_incr >= 0x80:
-            line_incr -= 0x100
-        lineno += line_incr
-
-(In C, this is implemented by PyCode_Addr2Line().)  In order for this to work,
-when the addr field increments by more than 255, the line # increment in each
-pair generated must be 0 until the remaining addr increment is < 256.  So, in
-the example above, assemble_lnotab in compile.c should not (as was actually done
-until 2.2) expand 300, 200 to
-    255, 255, 45, 45,
-but to
-    255, 0, 45, 127, 0, 73.
-
-The above is sufficient to reconstruct line numbers for tracebacks, but not for
-line tracing.  Tracing is handled by PyCode_CheckLineNumber() in codeobject.c
-and maybe_call_line_trace() in ceval.c.
-
-*** Tracing ***
-
-To a first approximation, we want to call the tracing function when the line
-number of the current instruction changes.  Re-computing the current line for
-every instruction is a little slow, though, so each time we compute the line
-number we save the bytecode indices where it's valid:
-
-     *instr_lb <= frame->f_lasti < *instr_ub
-
-is true so long as execution does not change lines.  That is, *instr_lb holds
-the first bytecode index of the current line, and *instr_ub holds the first
-bytecode index of the next line.  As long as the above expression is true,
-maybe_call_line_trace() does not need to call PyCode_CheckLineNumber().  Note
-that the same line may appear multiple times in the lnotab, either because the
-bytecode jumped more than 255 indices between line number changes or because
-the compiler inserted the same line twice.  Even in that case, *instr_ub holds
-the first index of the next line.
-
-However, we don't *always* want to call the line trace function when the above
-test fails.
-
-Consider this code:
-
-1: def f(a):
-2:    while a:
-3:       print(1)
-4:       break
-5:    else:
-6:       print(2)
-
-which compiles to this:
-
-  2           0 SETUP_LOOP              26 (to 28)
-        >>    2 LOAD_FAST                0 (a)
-              4 POP_JUMP_IF_FALSE       18
-
-  3           6 LOAD_GLOBAL              0 (print)
-              8 LOAD_CONST               1 (1)
-             10 CALL_NO_KW               1
-             12 POP_TOP
-
-  4          14 BREAK_LOOP
-             16 JUMP_ABSOLUTE            2
-        >>   18 POP_BLOCK
-
-  6          20 LOAD_GLOBAL              0 (print)
-             22 LOAD_CONST               2 (2)
-             24 CALL_NO_KW               1
-             26 POP_TOP
-        >>   28 LOAD_CONST               0 (None)
-             30 RETURN_VALUE
-
-If 'a' is false, execution will jump to the POP_BLOCK instruction at offset 18
-and the co_lnotab will claim that execution has moved to line 4, which is wrong.
-In this case, we could instead associate the POP_BLOCK with line 5, but that
-would break jumps around loops without else clauses.
-
-We fix this by only calling the line trace function for a forward jump if the
-co_lnotab indicates we have jumped to the *start* of a line, i.e. if the current
-instruction offset matches the offset given for the start of a line by the
-co_lnotab.  For backward jumps, however, we always call the line trace function,
-which lets a debugger stop on every evaluation of a loop guard (which usually
-won't be the first opcode in a line).
-
-Why do we set f_lineno when tracing, and only just before calling the trace
-function?  Well, consider the code above when 'a' is true.  If stepping through
-this with 'n' in pdb, you would stop at line 1 with a "call" type event, then
-line events on lines 2, 3, and 4, then a "return" type event -- but because the
-code for the return actually falls in the range of the "line 6" opcodes, you
-would be shown line 6 during this event.  This is a change from the behaviour in
-2.2 and before, and I've found it confusing in practice.  By setting and using
-f_lineno when tracing, one can report a line number different from that
-suggested by f_lasti on this one occasion where it's desirable.