Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/deprecations/pending-removal-in-3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Doc/deprecations/pending-removal-in-future.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
24 changes: 15 additions & 9 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,7 @@ Attribute assignment updates the module's namespace dictionary, e.g.,
single: __doc__ (module attribute)
single: __annotations__ (module attribute)
single: __annotate__ (module attribute)
single: __lazy_modules__ (module attribute)
pair: module; namespace

.. _import-mod-attrs:
Expand Down Expand Up @@ -1121,6 +1122,20 @@ the following writable attributes:

.. versionadded:: 3.14

.. attribute:: module.__lazy_modules__

A container (an object implementing :meth:`~object.__contains__`) of fully
qualified module name strings. When defined
at module scope, any regular :keyword:`import` statement in that module whose
target module name appears in this container is treated as a
:ref:`lazy import <lazy-imports>`, as if the :keyword:`lazy` keyword had
been used. Imports inside functions, class bodies, or
:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are unaffected.

See :ref:`lazy-modules-compat` for details and examples.

.. versionadded:: 3.15

Module dictionaries
^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -1461,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)
Expand Down Expand Up @@ -1534,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

Expand Down
50 changes: 50 additions & 0 deletions Doc/reference/simple_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,56 @@ See :pep:`810` for the full specification of lazy imports.

.. versionadded:: 3.15

.. _lazy-modules-compat:

Compatibility via ``__lazy_modules__``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. index::
single: __lazy_modules__

As an alternative to using the :keyword:`lazy` keyword, a module can opt
into lazy loading for specific imports by defining a module-level
:attr:`~module.__lazy_modules__` variable. When present, it must be a
container of fully qualified module name strings. Any regular (non-``lazy``)
:keyword:`import` statement at module scope whose target appears in
:attr:`!__lazy_modules__` is treated as a lazy import, exactly as if the
:keyword:`lazy` keyword had been used.

This provides a way to enable lazy loading for specific dependencies without
changing individual ``import`` statements. This is useful when supporting
Python versions older than 3.15 while using lazy imports in 3.15+::

__lazy_modules__ = ["json", "pathlib"]

import json # loaded lazily (name is in __lazy_modules__)
import os # loaded eagerly (name not in __lazy_modules__)

import pathlib # loaded lazily

Relative imports are resolved to their absolute name before the lookup, so
:attr:`!__lazy_modules__` must always contain fully qualified module names.

For ``from``-style imports, the relevant name is the module following
``from``, not the names of its members::

# In mypackage/mymodule.py
__lazy_modules__ = ["mypackage", "mypackage.sub.utils"]

from . import helper # loaded lazily: . resolves to mypackage
from .sub.utils import func # loaded lazily: .sub.utils resolves to mypackage.sub.utils
import json # loaded eagerly (not in __lazy_modules__)

Imports inside functions, class bodies, or
:keyword:`try`/:keyword:`except`/:keyword:`finally` blocks are always eager,
regardless of :attr:`!__lazy_modules__`.

Setting ``-X lazy_imports=none`` (or the :envvar:`PYTHON_LAZY_IMPORTS`
environment variable to ``none``) overrides :attr:`!__lazy_modules__` and
forces all imports to be eager.

.. versionadded:: 3.15

.. _future:

Future statements
Expand Down
2 changes: 1 addition & 1 deletion Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ function, class body, or ``try``/``except``/``finally`` block raises a
(``lazy from module import *`` and ``lazy from __future__ import ...`` both
raise :exc:`SyntaxError`).

For code that cannot use the ``lazy`` keyword directly (for example, when
supporting Python versions older than 3.15 while still using lazy
imports on 3.15+), a module can define
:attr:`~module.__lazy_modules__` as a container of fully qualified module
name strings. Regular ``import`` statements for those modules are then treated
as lazy, with the same semantics as the ``lazy`` keyword::

__lazy_modules__ = ["json", "pathlib"]

import json # lazy
import os # still eager

.. seealso:: :pep:`810` for the full specification and rationale.

(Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.)
Expand Down Expand Up @@ -1646,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
------

Expand Down
2 changes: 1 addition & 1 deletion Doc/whatsnew/3.6.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
8 changes: 0 additions & 8 deletions InternalDocs/code_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 8 additions & 4 deletions Lib/asyncio/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,15 @@ def run(self):

if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")):
sys.audit("cpython.run_startup", startup_path)

import tokenize
with tokenize.open(startup_path) as f:
startup_code = compile(f.read(), startup_path, "exec")
try:
import tokenize
with tokenize.open(startup_path) as f:
startup_code = compile(f.read(), startup_path, "exec")
exec(startup_code, console.locals)
except SystemExit:
raise
except BaseException:
console.showtraceback()

ps1 = getattr(sys, "ps1", ">>> ")
if CAN_USE_PYREPL:
Expand Down
14 changes: 11 additions & 3 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1298,10 +1298,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
# This function doesn't reference __class__, so nothing to do.
return False
# Fix the cell to point to the new class, if it's already pointing
# at the old class. I'm not convinced that the "is oldcls" test
# is needed, but other than performance can't hurt.
# at the old class.
closure = f.__closure__[idx]
if closure.cell_contents is oldcls:

try:
contents = closure.cell_contents
except ValueError:
# Cell is empty
return False

# This check makes it so we avoid updating an incorrect cell if the
# class body contains a function that was defined in a different class.
if contents is oldcls:
closure.cell_contents = newcls
return True
return False
Expand Down
2 changes: 0 additions & 2 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 0 additions & 7 deletions Lib/test/test_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,13 +424,6 @@ def func():
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
Expand Down
46 changes: 46 additions & 0 deletions Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5375,5 +5375,51 @@ def cls(self):
# one will be keeping a reference to the underlying class A.
self.assertIs(A().cls(), B)

def test_empty_class_cell(self):
# gh-148947: Make sure that we explicitly handle the empty class cell.
def maker():
if False:
__class__ = 42

def method(self):
return __class__
return method

from dataclasses import dataclass

@dataclass(slots=True)
class X:
a: int

meth = maker()

with self.assertRaisesRegex(NameError, '__class__'):
X(1).meth()

def test_class_cell_from_other_class(self):
# This test fails without the "is oldcls" check in
# _update_func_cell_for__class__.
class Base:
def meth(self):
return "Base"

class Child(Base):
def meth(self):
return super().meth() + " Child"

@dataclass(slots=True)
class DC(Child):
a: int

meth = Child.meth

closure = DC.meth.__closure__
self.assertEqual(len(closure), 1)
self.assertIs(closure[0].cell_contents, Child)

self.assertEqual(DC(1).meth(), "Base Child")



if __name__ == '__main__':
unittest.main()
Loading
Loading