This commit is contained in:
Pablo Galindo Salgado 2025-12-07 21:28:40 -08:00 committed by GitHub
commit c8f14db7c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
133 changed files with 4733 additions and 174 deletions

View file

@ -341,6 +341,58 @@ Importing Modules
.. versionadded:: 3.14 .. versionadded:: 3.14
.. c:function:: PyImport_LazyImportsMode PyImport_GetLazyImportsMode()
Gets the current lazy imports mode.
.. versionadded:: next
.. c:function:: PyObject* PyImport_GetLazyImportsFilter()
Return a :term:`strong reference` to the current lazy imports filter,
or ``NULL`` if none exists. This function always succeeds.
.. versionadded:: next
.. c:function:: int PyImport_SetLazyImportsMode(PyImport_LazyImportsMode mode)
Similar to :c:func:`PyImport_ImportModuleAttr`, but names are UTF-8 encoded
strings instead of Python :class:`str` objects.
This function always returns ``0``.
.. versionadded:: next
.. c:function:: int PyImport_SetLazyImportsFilter(PyObject *filter)
Sets the current lazy imports filter. The *filter* should be a callable that
will receive ``(importing_module_name, imported_module_name, [fromlist])``
when an import can potentially be lazy and that must return ``True`` if
the import should be lazy and ``False`` otherwise.
Return ``0`` on success and ``-1`` with an exception set otherwise.
.. versionadded:: next
.. c:type:: PyImport_LazyImportsMode
Enumeration of possible lazy import modes.
.. c:enumerator:: PyImport_LAZY_NORMAL
Respect the ``lazy`` keyword in source code. This is the default mode.
.. c:enumerator:: PyImport_LAZY_ALL
Make all imports lazy by default.
.. c:enumerator:: PyImport_LAZY_NONE
Disable lazy imports entirely. Even explicit ``lazy`` statements become
eager imports.
.. versionadded:: next
.. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void)) .. c:function:: PyObject* PyImport_CreateModuleFromInitfunc(PyObject *spec, PyObject* (*initfunc)(void))
This function is a building block that enables embedders to implement This function is a building block that enables embedders to implement

View file

@ -1113,7 +1113,8 @@ Imports
names=[ names=[
alias(name='x'), alias(name='x'),
alias(name='y'), alias(name='y'),
alias(name='z')])]) alias(name='z')],
is_lazy=0)])
.. class:: ImportFrom(module, names, level) .. class:: ImportFrom(module, names, level)
@ -1134,7 +1135,8 @@ Imports
alias(name='x'), alias(name='x'),
alias(name='y'), alias(name='y'),
alias(name='z')], alias(name='z')],
level=0)]) level=0,
is_lazy=0)])
.. class:: alias(name, asname) .. class:: alias(name, asname)
@ -1152,7 +1154,8 @@ Imports
names=[ names=[
alias(name='a', asname='b'), alias(name='a', asname='b'),
alias(name='c')], alias(name='c')],
level=2)]) level=2,
is_lazy=0)])
Control flow Control flow
^^^^^^^^^^^^ ^^^^^^^^^^^^

View file

@ -911,6 +911,43 @@ always available. Unless explicitly noted otherwise, all variables are read-only
.. versionadded:: 3.11 .. versionadded:: 3.11
.. function:: get_lazy_imports()
Returns the current lazy imports mode as a string.
* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword are lazy
* ``"all"``: All top-level imports are potentially lazy
* ``"none"``: All lazy imports are suppressed (even explicitly marked ones)
See also :func:`set_lazy_imports` and :pep:`810`.
.. versionadded:: 3.15
.. function:: get_lazy_imports_filter()
Returns the current lazy imports filter function, or ``None`` if no filter
is set.
The filter function is called for every potentially lazy import to determine
whether it should actually be lazy. See :func:`set_lazy_imports_filter` for
details on the filter function signature.
.. versionadded:: 3.15
.. function:: get_lazy_modules()
Returns a set of fully-qualified module names that have been lazily imported.
This is primarily useful for diagnostics and introspection.
Note that modules are removed from this set when they are reified (actually
loaded on first use).
.. versionadded:: 3.15
.. function:: getrefcount(object) .. function:: getrefcount(object)
Return the reference count of the *object*. The count returned is generally one Return the reference count of the *object*. The count returned is generally one
@ -1719,6 +1756,57 @@ always available. Unless explicitly noted otherwise, all variables are read-only
.. versionadded:: 3.11 .. versionadded:: 3.11
.. function:: set_lazy_imports(mode)
Sets the global lazy imports mode. The *mode* parameter must be one of the
following strings:
* ``"normal"``: Only imports explicitly marked with the ``lazy`` keyword are lazy
* ``"all"``: All top-level imports become potentially lazy
* ``"none"``: All lazy imports are suppressed (even explicitly marked ones)
This function is intended for advanced users who need to control lazy imports
across their entire application. Library developers should generally not use
this function as it affects the runtime execution of applications.
In addition to the mode, lazy imports can be controlled via the filter
provided by :func:`set_lazy_imports_filter`.
See also :func:`get_lazy_imports` and :pep:`810`.
.. versionadded:: 3.15
.. function:: set_lazy_imports_filter(filter)
Sets the lazy imports filter callback. The *filter* parameter must be a
callable or ``None`` to clear the filter.
The filter function is called for every potentially lazy import to determine
whether it should actually be lazy. It must have the following signature::
def filter(importing_module: str, imported_module: str,
fromlist: tuple[str, ...] | None) -> bool
Where:
* *importing_module* is the name of the module doing the import
* *imported_module* is the name of the module being imported
* *fromlist* is the tuple of names being imported (for ``from ... import``
statements), or ``None`` for regular imports
The filter should return ``True`` to allow the import to be lazy, or
``False`` to force an eager import.
This is an advanced feature intended for specialized users who need
fine-grained control over lazy import behavior.
See also :func:`get_lazy_imports_filter` and :pep:`810`.
.. versionadded:: 3.15
.. function:: setprofile(profilefunc) .. function:: setprofile(profilefunc)
.. index:: .. index::

View file

@ -343,6 +343,18 @@ Standard names are defined for the following types:
.. seealso:: :pep:`667` .. seealso:: :pep:`667`
.. data:: LazyImportType
The type of lazy import proxy objects. These objects are created when a
module is lazily imported and serve as placeholders until the module is
actually accessed. This type can be used to detect lazy imports
programmatically.
.. versionadded:: next
.. seealso:: :pep:`810`
.. data:: GetSetDescriptorType .. data:: GetSetDescriptorType
The type of objects defined in extension modules with ``PyGetSetDef``, such The type of objects defined in extension modules with ``PyGetSetDef``, such

View file

@ -457,6 +457,7 @@ Some names are only reserved under specific contexts. These are known as
- ``match``, ``case``, and ``_``, when used in the :keyword:`match` statement. - ``match``, ``case``, and ``_``, when used in the :keyword:`match` statement.
- ``type``, when used in the :keyword:`type` statement. - ``type``, when used in the :keyword:`type` statement.
- ``lazy``, when used before an :keyword:`import` statement.
These syntactically act as keywords in their specific contexts, These syntactically act as keywords in their specific contexts,
but this distinction is done at the parser level, not when tokenizing. but this distinction is done at the parser level, not when tokenizing.
@ -468,6 +469,9 @@ identifier names.
.. versionchanged:: 3.12 .. versionchanged:: 3.12
``type`` is now a soft keyword. ``type`` is now a soft keyword.
.. versionchanged:: next
``lazy`` is now a soft keyword.
.. index:: .. index::
single: _, identifiers single: _, identifiers
single: __, identifiers single: __, identifiers

View file

@ -748,14 +748,15 @@ The :keyword:`!import` statement
pair: name; binding pair: name; binding
pair: keyword; from pair: keyword; from
pair: keyword; as pair: keyword; as
pair: keyword; lazy
pair: exception; ImportError pair: exception; ImportError
single: , (comma); import statement single: , (comma); import statement
.. productionlist:: python-grammar .. productionlist:: python-grammar
import_stmt: "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])* import_stmt: ["lazy"] "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
: | "from" `relative_module` "import" `identifier` ["as" `identifier`] : | ["lazy"] "from" `relative_module` "import" `identifier` ["as" `identifier`]
: ("," `identifier` ["as" `identifier`])* : ("," `identifier` ["as" `identifier`])*
: | "from" `relative_module` "import" "(" `identifier` ["as" `identifier`] : | ["lazy"] "from" `relative_module` "import" "(" `identifier` ["as" `identifier`]
: ("," `identifier` ["as" `identifier`])* [","] ")" : ("," `identifier` ["as" `identifier`])* [","] ")"
: | "from" `relative_module` "import" "*" : | "from" `relative_module` "import" "*"
module: (`identifier` ".")* `identifier` module: (`identifier` ".")* `identifier`
@ -870,6 +871,56 @@ determine dynamically the modules to be loaded.
.. audit-event:: import module,filename,sys.path,sys.meta_path,sys.path_hooks import .. audit-event:: import module,filename,sys.path,sys.meta_path,sys.path_hooks import
.. _lazy-imports:
.. _lazy:
Lazy imports
------------
.. index::
pair: lazy; import
single: lazy import
The :keyword:`lazy` keyword marks an import as lazy. It is a :ref:`soft keyword
<soft-keywords>` that only has special meaning when it appears immediately
before an :keyword:`import` or :keyword:`from` statement.
When an import statement is preceded by the :keyword:`lazy` keyword,
the import becomes *lazy*: the module is not loaded immediately at the import
statement. Instead, a lazy proxy object is created and bound to the name. The
actual module is loaded on first use of that name.
Lazy imports are only permitted at module scope. Using ``lazy`` inside a
function, class body, or :keyword:`try`/:keyword:`except`/:keyword:`finally`
block raises a :exc:`SyntaxError`. Star imports cannot be lazy (``lazy from
module import *`` is a syntax error), and :ref:`future statements <future>`
cannot be lazy.
When using ``lazy from ... import``, each imported name is bound to a lazy
proxy object. The first access to any of these names triggers loading of the
entire module and resolves only that specific name to its actual value. Other
names remain as lazy proxies until they are accessed.
Example::
lazy import json
print('json' in sys.modules) # False - module not loaded yet
# First use triggers loading
result = json.dumps({"hello": "world"})
print('json' in sys.modules) # True - now loaded
If an error occurs during module loading (such as :exc:`ImportError` or
:exc:`SyntaxError`), it is raised at the point where the lazy import is first
used, not at the import statement itself.
See :pep:`810` for the full specification of lazy imports.
.. versionadded:: next
.. _future: .. _future:
Future statements Future statements

View file

@ -694,6 +694,14 @@ Miscellaneous options
.. versionadded:: 3.14 .. versionadded:: 3.14
* :samp:`-X lazy_imports={all,none,normal}` controls lazy import behavior.
``all`` makes all imports lazy by default, ``none`` disables lazy imports
entirely (even explicit ``lazy`` statements become eager), and ``normal``
(the default) respects the ``lazy`` keyword in source code.
See also :envvar:`PYTHON_LAZY_IMPORTS`.
.. versionadded:: next
It also allows passing arbitrary values and retrieving them through the It also allows passing arbitrary values and retrieving them through the
:data:`sys._xoptions` dictionary. :data:`sys._xoptions` dictionary.
@ -1339,6 +1347,17 @@ conflict.
.. versionadded:: 3.14 .. versionadded:: 3.14
.. envvar:: PYTHON_LAZY_IMPORTS
Controls lazy import behavior. Accepts three values: ``all`` makes all
imports lazy by default, ``none`` disables lazy imports entirely (even
explicit ``lazy`` statements become eager), and ``normal`` (the default)
respects the ``lazy`` keyword in source code.
See also the :option:`-X lazy_imports <-X>` command-line option.
.. versionadded:: next
Debug-mode variables Debug-mode variables
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View file

@ -65,6 +65,8 @@ Summary -- Release highlights
.. PEP-sized items next. .. PEP-sized items next.
* :pep:`810`: :ref:`Explicit lazy imports for faster startup times
<whatsnew315-pep810>`
* :pep:`799`: :ref:`A dedicated profiling package for organizing Python * :pep:`799`: :ref:`A dedicated profiling package for organizing Python
profiling tools <whatsnew315-profiling-package>` profiling tools <whatsnew315-profiling-package>`
* :pep:`686`: :ref:`Python now uses UTF-8 as the default encoding * :pep:`686`: :ref:`Python now uses UTF-8 as the default encoding
@ -77,6 +79,98 @@ Summary -- Release highlights
New features New features
============ ============
.. _whatsnew315-pep810:
:pep:`810`: Explicit lazy imports
---------------------------------
Large Python applications often suffer from slow startup times. A significant
contributor to this problem is the import system: when a module is imported,
Python must locate the file, read it from disk, compile it to bytecode, and
execute all top-level code. For applications with deep dependency trees, this
process can take seconds, even when most of the imported code is never actually
used during a particular run.
Developers have worked around this by moving imports inside functions, using
:mod:`importlib` to load modules on demand, or restructuring code to avoid
unnecessary dependencies. These approaches work but make code harder to read
and maintain, scatter import statements throughout the codebase, and require
discipline to apply consistently.
Python now provides a cleaner solution through explicit lazy imports using the
new ``lazy`` soft keyword. When you mark an import as lazy, Python defers the
actual module loading until the imported name is first used. This gives you
the organizational benefits of declaring all imports at the top of the file
while only paying the loading cost for modules you actually use.
The ``lazy`` keyword works with both ``import`` and ``from ... import`` statements.
When you write ``lazy import heavy_module``, Python does not immediately load the
module. Instead, it creates a lightweight proxy object. The actual module loading
happens transparently when you first access the name:
.. code-block:: python
lazy import json
lazy from datetime import datetime
print("Starting up...") # json and datetime not loaded yet
data = json.loads('{"key": "value"}') # json loads here
now = datetime() # datetime loads here
This mechanism is particularly useful for applications that import many modules
at the top level but may only use a subset of them in any given run. The deferred
loading reduces startup latency without requiring code restructuring or conditional
imports scattered throughout the codebase.
When a lazy import eventually fails (for example, if the module does not exist),
Python raises the exception at the point of first use rather than at import time.
The traceback includes both the location where the name was accessed and the
original import statement, making it straightforward to diagnose the problem.
For cases where you want to enable lazy loading globally without modifying source
code, Python provides the :option:`-X lazy_imports <-X>` command-line option and
the :envvar:`PYTHON_LAZY_IMPORTS` environment variable. Both accept three values:
``all`` makes all imports lazy by default, ``none`` disables lazy imports entirely
(even explicit ``lazy`` statements become eager), and ``normal`` (the default)
respects the ``lazy`` keyword in source code. The :func:`sys.set_lazy_imports` and
:func:`sys.get_lazy_imports` functions allow changing and querying this mode at
runtime.
For more selective control, :func:`sys.set_lazy_imports_filter` accepts a callable
that determines whether a specific module should be loaded lazily. The filter
receives three arguments: the importing module's name (or ``None``), the imported
module's name, and the fromlist (or ``None`` for regular imports). It should
return ``True`` to allow the import to be lazy, or ``False`` to force eager loading.
This allows patterns like making only your own application's modules lazy while
keeping third-party dependencies eager:
.. code-block:: python
import sys
sys.set_lazy_imports_filter(lambda importing, imported, fromlist: imported.startswith("myapp."))
sys.set_lazy_imports("all")
import myapp.slow_module # lazy (matches filter)
import json # eager (does not match filter)
For debugging and introspection, :func:`sys.get_lazy_modules` returns a set
containing the names of all modules that have been lazily imported but not yet
loaded. The proxy type itself is available as :data:`types.LazyImportType` for
code that needs to detect lazy imports programmatically.
There are some restrictions on where ``lazy`` can appear. Lazy imports are only
permitted at module scope; using ``lazy`` inside a function, class body, or
``try``/``except``/``finally`` block raises a :exc:`SyntaxError`. Star imports
cannot be lazy (``lazy from module import *`` is a syntax error), and future
imports cannot be lazy either (``lazy from __future__ import ...`` raises
:exc:`SyntaxError`).
.. seealso:: :pep:`810` for the full specification and rationale.
(Contributed by Pablo Galindo Salgado and Dino Viehland in :gh:`142349`.)
.. _whatsnew315-profiling-package: .. _whatsnew315-profiling-package:
:pep:`799`: A dedicated profiling package :pep:`799`: A dedicated profiling package

View file

@ -121,9 +121,9 @@ simple_stmts[asdl_stmt_seq*]:
simple_stmt[stmt_ty] (memo): simple_stmt[stmt_ty] (memo):
| assignment | assignment
| &"type" type_alias | &"type" type_alias
| &('import' | 'from' | "lazy") import_stmt
| e=star_expressions { _PyAST_Expr(e, EXTRA) } | e=star_expressions { _PyAST_Expr(e, EXTRA) }
| &'return' return_stmt | &'return' return_stmt
| &('import' | 'from') import_stmt
| &'raise' raise_stmt | &'raise' raise_stmt
| &'pass' pass_stmt | &'pass' pass_stmt
| &'del' del_stmt | &'del' del_stmt
@ -216,7 +216,7 @@ assert_stmt[stmt_ty]:
| invalid_assert_stmt | invalid_assert_stmt
| 'assert' a=expression b=[',' z=expression { z }] { _PyAST_Assert(a, b, EXTRA) } | 'assert' a=expression b=[',' z=expression { z }] { _PyAST_Assert(a, b, EXTRA) }
import_stmt[stmt_ty]: import_stmt[stmt_ty](memo):
| invalid_import | invalid_import
| import_name | import_name
| import_from | import_from
@ -224,13 +224,15 @@ import_stmt[stmt_ty]:
# Import statements # Import statements
# ----------------- # -----------------
import_name[stmt_ty]: 'import' a=dotted_as_names { _PyAST_Import(a, EXTRA) } import_name[stmt_ty]:
| lazy="lazy"? 'import' a=dotted_as_names { _PyAST_Import(a, lazy ? 1 : 0, EXTRA) }
# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS # note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS
import_from[stmt_ty]: import_from[stmt_ty]:
| 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets { | lazy="lazy"? 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
_PyPegen_checked_future_import(p, b->v.Name.id, c, _PyPegen_seq_count_dots(a), EXTRA) } _PyPegen_checked_future_import(p, b->v.Name.id, c, _PyPegen_seq_count_dots(a), lazy, EXTRA) }
| 'from' a=('.' | '...')+ 'import' b=import_from_targets { | lazy="lazy"? 'from' a=('.' | '...')+ 'import' b=import_from_targets {
_PyAST_ImportFrom(NULL, b, _PyPegen_seq_count_dots(a), EXTRA) } _PyAST_ImportFrom(NULL, b, _PyPegen_seq_count_dots(a), lazy ? 1 : 0, EXTRA) }
import_from_targets[asdl_alias_seq*]: import_from_targets[asdl_alias_seq*]:
| '(' a=import_from_as_names [','] ')' { a } | '(' a=import_from_as_names [','] ')' { a }
| import_from_as_names !',' | import_from_as_names !','

View file

@ -190,6 +190,7 @@ typedef struct PyConfig {
int enable_gil; int enable_gil;
int tlbc_enabled; int tlbc_enabled;
#endif #endif
int lazy_imports;
/* --- Path configuration inputs ------------ */ /* --- Path configuration inputs ------------ */
int pathconfig_warnings; int pathconfig_warnings;

View file

@ -88,6 +88,20 @@ PyAPI_FUNC(int) PyImport_AppendInittab(
PyObject* (*initfunc)(void) PyObject* (*initfunc)(void)
); );
typedef enum {
PyImport_LAZY_NORMAL,
PyImport_LAZY_ALL,
PyImport_LAZY_NONE,
} PyImport_LazyImportsMode;
#ifndef Py_LIMITED_API
PyAPI_FUNC(int) PyImport_SetLazyImportsMode(PyImport_LazyImportsMode mode);
PyAPI_FUNC(int) PyImport_SetLazyImportsFilter(PyObject *filter);
PyAPI_FUNC(PyImport_LazyImportsMode) PyImport_GetLazyImportsMode(void);
PyAPI_FUNC(PyObject *) PyImport_GetLazyImportsFilter(void);
#endif
#ifndef Py_LIMITED_API #ifndef Py_LIMITED_API
# define Py_CPYTHON_IMPORT_H # define Py_CPYTHON_IMPORT_H
# include "cpython/import.h" # include "cpython/import.h"

View file

@ -329,12 +329,14 @@ struct _stmt {
struct { struct {
asdl_alias_seq *names; asdl_alias_seq *names;
int is_lazy;
} Import; } Import;
struct { struct {
identifier module; identifier module;
asdl_alias_seq *names; asdl_alias_seq *names;
int level; int level;
int is_lazy;
} ImportFrom; } ImportFrom;
struct { struct {
@ -764,11 +766,12 @@ stmt_ty _PyAST_TryStar(asdl_stmt_seq * body, asdl_excepthandler_seq * handlers,
end_col_offset, PyArena *arena); end_col_offset, PyArena *arena);
stmt_ty _PyAST_Assert(expr_ty test, expr_ty msg, int lineno, int col_offset, stmt_ty _PyAST_Assert(expr_ty test, expr_ty msg, int lineno, int col_offset,
int end_lineno, int end_col_offset, PyArena *arena); int end_lineno, int end_col_offset, PyArena *arena);
stmt_ty _PyAST_Import(asdl_alias_seq * names, int lineno, int col_offset, int stmt_ty _PyAST_Import(asdl_alias_seq * names, int is_lazy, int lineno, int
end_lineno, int end_col_offset, PyArena *arena); col_offset, int end_lineno, int end_col_offset, PyArena
*arena);
stmt_ty _PyAST_ImportFrom(identifier module, asdl_alias_seq * names, int level, stmt_ty _PyAST_ImportFrom(identifier module, asdl_alias_seq * names, int level,
int lineno, int col_offset, int end_lineno, int int is_lazy, int lineno, int col_offset, int
end_col_offset, PyArena *arena); end_lineno, int end_col_offset, PyArena *arena);
stmt_ty _PyAST_Global(asdl_identifier_seq * names, int lineno, int col_offset, stmt_ty _PyAST_Global(asdl_identifier_seq * names, int lineno, int col_offset,
int end_lineno, int end_col_offset, PyArena *arena); int end_lineno, int end_col_offset, PyArena *arena);
stmt_ty _PyAST_Nonlocal(asdl_identifier_seq * names, int lineno, int stmt_ty _PyAST_Nonlocal(asdl_identifier_seq * names, int lineno, int

View file

@ -205,6 +205,7 @@ struct ast_state {
PyObject *id; PyObject *id;
PyObject *ifs; PyObject *ifs;
PyObject *is_async; PyObject *is_async;
PyObject *is_lazy;
PyObject *items; PyObject *items;
PyObject *iter; PyObject *iter;
PyObject *key; PyObject *key;

View file

@ -311,7 +311,14 @@ PyAPI_FUNC(void) _PyEval_FormatExcCheckArg(PyThreadState *tstate, PyObject *exc,
PyAPI_FUNC(void) _PyEval_FormatExcUnbound(PyThreadState *tstate, PyCodeObject *co, int oparg); PyAPI_FUNC(void) _PyEval_FormatExcUnbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
PyAPI_FUNC(void) _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwargs); PyAPI_FUNC(void) _PyEval_FormatKwargsError(PyThreadState *tstate, PyObject *func, PyObject *kwargs);
PyAPI_FUNC(PyObject *) _PyEval_ImportFrom(PyThreadState *, PyObject *, PyObject *); PyAPI_FUNC(PyObject *) _PyEval_ImportFrom(PyThreadState *, PyObject *, PyObject *);
PyAPI_FUNC(PyObject *) _PyEval_ImportName(PyThreadState *, _PyInterpreterFrame *, PyObject *, PyObject *, PyObject *); PyAPI_FUNC(PyObject *) _PyEval_LazyImportName(PyThreadState *tstate, PyObject *builtins, PyObject *globals,
PyObject *locals, PyObject *name, PyObject *fromlist, PyObject *level, int lazy);
PyAPI_FUNC(PyObject *) _PyEval_LazyImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name);
PyAPI_FUNC(PyObject *) _PyEval_ImportName(PyThreadState *tstate, PyObject *builtins, PyObject *globals, PyObject *locals,
PyObject *name, PyObject *fromlist, PyObject *level);
PyObject *
_PyEval_ImportNameWithImport(PyThreadState *tstate, PyObject *import_func, PyObject *globals, PyObject *locals,
PyObject *name, PyObject *fromlist, PyObject *level);
PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs); PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs);
PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys); PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys);
PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr); PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr);

View file

@ -131,6 +131,7 @@ int _PyCompile_PushFBlock(struct _PyCompiler *c, _Py_SourceLocation loc,
void _PyCompile_PopFBlock(struct _PyCompiler *c, enum _PyCompile_FBlockType t, void _PyCompile_PopFBlock(struct _PyCompiler *c, enum _PyCompile_FBlockType t,
_PyJumpTargetLabel block_label); _PyJumpTargetLabel block_label);
_PyCompile_FBlockInfo *_PyCompile_TopFBlock(struct _PyCompiler *c); _PyCompile_FBlockInfo *_PyCompile_TopFBlock(struct _PyCompiler *c);
bool _PyCompile_InExceptionHandler(struct _PyCompiler *c);
int _PyCompile_EnterScope(struct _PyCompiler *c, identifier name, int scope_type, int _PyCompile_EnterScope(struct _PyCompiler *c, identifier name, int scope_type,
void *key, int lineno, PyObject *private, void *key, int lineno, PyObject *private,

View file

@ -36,6 +36,15 @@ extern int _PyDict_DelItem_KnownHash_LockHeld(PyObject *mp, PyObject *key,
extern int _PyDict_Contains_KnownHash(PyObject *, PyObject *, Py_hash_t); extern int _PyDict_Contains_KnownHash(PyObject *, PyObject *, Py_hash_t);
// "Id" variants
extern PyObject* _PyDict_GetItemIdWithError(PyObject *dp,
_Py_Identifier *key);
extern int _PyDict_ContainsId(PyObject *, _Py_Identifier *);
extern int _PyDict_SetItemId(PyObject *dp, _Py_Identifier *key, PyObject *item);
extern int _PyDict_DelItemId(PyObject *mp, _Py_Identifier *key);
extern void _PyDict_ClearKeysVersion(PyObject *mp);
extern int _PyDict_Next( extern int _PyDict_Next(
PyObject *mp, Py_ssize_t *pos, PyObject **key, PyObject **value, Py_hash_t *hash); PyObject *mp, Py_ssize_t *pos, PyObject **key, PyObject **value, Py_hash_t *hash);

View file

@ -1434,6 +1434,8 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__iter__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__iter__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__itruediv__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__itruediv__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__ixor__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__ixor__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__lazy_import__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__lazy_modules__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__le__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__le__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__len__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__len__));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__length_hint__)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__length_hint__));

View file

@ -157,6 +157,8 @@ struct _Py_global_strings {
STRUCT_FOR_ID(__iter__) STRUCT_FOR_ID(__iter__)
STRUCT_FOR_ID(__itruediv__) STRUCT_FOR_ID(__itruediv__)
STRUCT_FOR_ID(__ixor__) STRUCT_FOR_ID(__ixor__)
STRUCT_FOR_ID(__lazy_import__)
STRUCT_FOR_ID(__lazy_modules__)
STRUCT_FOR_ID(__le__) STRUCT_FOR_ID(__le__)
STRUCT_FOR_ID(__len__) STRUCT_FOR_ID(__len__)
STRUCT_FOR_ID(__length_hint__) STRUCT_FOR_ID(__length_hint__)

View file

@ -32,6 +32,19 @@ extern int _PyImport_FixupBuiltin(
PyObject *modules PyObject *modules
); );
extern PyObject *
_PyImport_ResolveName(PyThreadState *tstate, PyObject *name, PyObject *globals, int level);
extern PyObject *
_PyImport_GetAbsName(PyThreadState *tstate, PyObject *name, PyObject *globals, int level);
// Symbol is exported for the JIT on Windows builds.
PyAPI_FUNC(PyObject *)
_PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import);
extern PyObject *
_PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, PyObject *name, PyObject *builtins, PyObject *globals,
PyObject *locals, PyObject *fromlist,
int level);
#ifdef HAVE_DLOPEN #ifdef HAVE_DLOPEN
# include <dlfcn.h> // RTLD_NOW, RTLD_LAZY # include <dlfcn.h> // RTLD_NOW, RTLD_LAZY
# if HAVE_DECL_RTLD_NOW # if HAVE_DECL_RTLD_NOW
@ -74,6 +87,10 @@ extern int _PyImport_IsDefaultImportFunc(
PyInterpreterState *interp, PyInterpreterState *interp,
PyObject *func); PyObject *func);
extern int _PyImport_IsDefaultLazyImportFunc(
PyInterpreterState *interp,
PyObject *func);
extern PyObject * _PyImport_GetImportlibLoader( extern PyObject * _PyImport_GetImportlibLoader(
PyInterpreterState *interp, PyInterpreterState *interp,
const char *loader_name); const char *loader_name);

View file

@ -322,6 +322,12 @@ struct _import_state {
int dlopenflags; int dlopenflags;
#endif #endif
PyObject *import_func; PyObject *import_func;
PyObject *lazy_import_func;
int lazy_imports_mode;
PyObject *lazy_imports_filter;
PyObject *lazy_importing_modules;
PyObject *lazy_modules;
PyObject *lazy_modules_set; /* Set of fully-qualified module names lazily imported (PEP 810) */
/* The global import lock. */ /* The global import lock. */
_PyRecursiveMutex lock; _PyRecursiveMutex lock;
/* diagnostic info in PyImport_ImportModuleLevelObject() */ /* diagnostic info in PyImport_ImportModuleLevelObject() */

View file

@ -0,0 +1,36 @@
/* File added for Lazy Imports */
/* Lazy object interface */
#ifndef Py_INTERNAL_LAZYIMPORTOBJECT_H
#define Py_INTERNAL_LAZYIMPORTOBJECT_H
#ifdef __cplusplus
extern "C" {
#endif
#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif
PyAPI_DATA(PyTypeObject) PyLazyImport_Type;
#define PyLazyImport_CheckExact(op) Py_IS_TYPE((op), &PyLazyImport_Type)
typedef struct {
PyObject_HEAD
PyObject *lz_builtins;
PyObject *lz_from;
PyObject *lz_attr;
/* Frame information for the original import location */
PyCodeObject *lz_code; /* code object where the lazy import was created */
int lz_instr_offset; /* instruction offset where the lazy import was created */
} PyLazyImportObject;
PyAPI_FUNC(PyObject *) _PyLazyImport_GetName(PyObject *lazy_import);
PyAPI_FUNC(PyObject *) _PyLazyImport_New(PyObject *import_func, PyObject *from, PyObject *attr);
#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_LAZYIMPORTOBJECT_H */

View file

@ -287,6 +287,7 @@ Known values:
Python 3.15a1 3654 (Fix missing exception handlers in logical expression) Python 3.15a1 3654 (Fix missing exception handlers in logical expression)
Python 3.15a1 3655 (Fix miscompilation of some module-level annotations) Python 3.15a1 3655 (Fix miscompilation of some module-level annotations)
Python 3.15a1 3656 (Add TRACE_RECORD instruction, for platforms with switch based interpreter) Python 3.15a1 3656 (Add TRACE_RECORD instruction, for platforms with switch based interpreter)
Python 3.15a3 3657 (Lazy imports IMPORT_NAME opcode changes)
Python 3.16 will start with 3700 Python 3.16 will start with 3700
@ -300,7 +301,7 @@ PC/launcher.c must also be updated.
*/ */
#define PYC_MAGIC_NUMBER 3656 #define PYC_MAGIC_NUMBER 3657
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes /* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */ (little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \ #define PYC_MAGIC_NUMBER_TOKEN \

View file

@ -28,6 +28,8 @@ typedef struct {
PyObject *md_weaklist; PyObject *md_weaklist;
// for logging purposes after md_dict is cleared // for logging purposes after md_dict is cleared
PyObject *md_name; PyObject *md_name;
// module version we last checked for lazy values
uint32_t m_dict_version;
bool md_token_is_def; /* if true, `md_token` is the PyModuleDef */ bool md_token_is_def; /* if true, `md_token` is the PyModuleDef */
#ifdef Py_GIL_DISABLED #ifdef Py_GIL_DISABLED
bool md_requires_gil; bool md_requires_gil;
@ -76,6 +78,8 @@ extern Py_ssize_t _PyModule_GetFilenameUTF8(
PyObject* _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress); PyObject* _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress);
PyObject* _Py_module_getattro(PyObject *m, PyObject *name); PyObject* _Py_module_getattro(PyObject *m, PyObject *name);
PyAPI_FUNC(int) _PyModule_ReplaceLazyValue(PyObject *dict, PyObject *name, PyObject *value);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -1432,6 +1432,8 @@ extern "C" {
INIT_ID(__iter__), \ INIT_ID(__iter__), \
INIT_ID(__itruediv__), \ INIT_ID(__itruediv__), \
INIT_ID(__ixor__), \ INIT_ID(__ixor__), \
INIT_ID(__lazy_import__), \
INIT_ID(__lazy_modules__), \
INIT_ID(__le__), \ INIT_ID(__le__), \
INIT_ID(__len__), \ INIT_ID(__len__), \
INIT_ID(__length_hint__), \ INIT_ID(__length_hint__), \

View file

@ -126,6 +126,7 @@ typedef struct _symtable_entry {
unsigned ste_method : 1; /* true if block is a function block defined in class scope */ unsigned ste_method : 1; /* true if block is a function block defined in class scope */
unsigned ste_has_conditional_annotations : 1; /* true if block has conditionally executed annotations */ unsigned ste_has_conditional_annotations : 1; /* true if block has conditionally executed annotations */
unsigned ste_in_conditional_block : 1; /* set while we are inside a conditionally executed block */ unsigned ste_in_conditional_block : 1; /* set while we are inside a conditionally executed block */
unsigned ste_in_try_block : 1; /* set while we are inside a try/except block */
unsigned ste_in_unevaluated_annotation : 1; /* set while we are processing an annotation that will not be evaluated */ unsigned ste_in_unevaluated_annotation : 1; /* set while we are processing an annotation that will not be evaluated */
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */ int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
_Py_SourceLocation ste_loc; /* source location of block */ _Py_SourceLocation ste_loc; /* source location of block */

View file

@ -408,6 +408,14 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string); _PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1); assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(__lazy_import__);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(__lazy_modules__);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(__le__); string = &_Py_ID(__le__);
_PyUnicode_InternStatic(interp, &string); _PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1)); assert(_PyUnicode_CheckConsistency(string, 1));

View file

@ -91,6 +91,9 @@ PyAPI_DATA(PyObject *) PyExc_EOFError;
PyAPI_DATA(PyObject *) PyExc_FloatingPointError; PyAPI_DATA(PyObject *) PyExc_FloatingPointError;
PyAPI_DATA(PyObject *) PyExc_OSError; PyAPI_DATA(PyObject *) PyExc_OSError;
PyAPI_DATA(PyObject *) PyExc_ImportError; PyAPI_DATA(PyObject *) PyExc_ImportError;
#if !defined(Py_LIMITED_API)
PyAPI_DATA(PyObject *) PyExc_ImportCycleError;
#endif
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03060000 #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03060000
PyAPI_DATA(PyObject *) PyExc_ModuleNotFoundError; PyAPI_DATA(PyObject *) PyExc_ModuleNotFoundError;
#endif #endif

View file

@ -240,6 +240,7 @@
REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'OSError') REVERSE_NAME_MAPPING[('builtins', excname)] = ('exceptions', 'OSError')
PYTHON3_IMPORTERROR_EXCEPTIONS = ( PYTHON3_IMPORTERROR_EXCEPTIONS = (
'ImportCycleError',
'ModuleNotFoundError', 'ModuleNotFoundError',
) )

View file

@ -276,6 +276,8 @@ def is_soft_keyword_used(*tokens: TI | None) -> bool:
TI(T.NAME, string=s) TI(T.NAME, string=s)
): ):
return not keyword.iskeyword(s) return not keyword.iskeyword(s)
case (None | TI(T.NEWLINE) | TI(T.INDENT) | TI(T.DEDENT), TI(string="lazy"), TI(string="import") | TI(string="from")):
return True
case _: case _:
return False return False

View file

@ -35,6 +35,7 @@
FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure', 'annotate') FUNCTION_ATTR_FLAGS = ('defaults', 'kwdefaults', 'annotations', 'closure', 'annotate')
ENTER_EXECUTOR = opmap['ENTER_EXECUTOR'] ENTER_EXECUTOR = opmap['ENTER_EXECUTOR']
IMPORT_NAME = opmap['IMPORT_NAME']
LOAD_GLOBAL = opmap['LOAD_GLOBAL'] LOAD_GLOBAL = opmap['LOAD_GLOBAL']
LOAD_SMALL_INT = opmap['LOAD_SMALL_INT'] LOAD_SMALL_INT = opmap['LOAD_SMALL_INT']
BINARY_OP = opmap['BINARY_OP'] BINARY_OP = opmap['BINARY_OP']
@ -601,6 +602,12 @@ def get_argval_argrepr(self, op, arg, offset):
argval, argrepr = _get_name_info(arg//4, get_name) argval, argrepr = _get_name_info(arg//4, get_name)
if (arg & 1) and argrepr: if (arg & 1) and argrepr:
argrepr = f"{argrepr} + NULL|self" argrepr = f"{argrepr} + NULL|self"
elif deop == IMPORT_NAME:
argval, argrepr = _get_name_info(arg//4, get_name)
if (arg & 1) and argrepr:
argrepr = f"{argrepr} + lazy"
elif (arg & 2) and argrepr:
argrepr = f"{argrepr} + eager"
else: else:
argval, argrepr = _get_name_info(arg, get_name) argval, argrepr = _get_name_info(arg, get_name)
elif deop in hasjump or deop in hasexc: elif deop in hasjump or deop in hasexc:
@ -1013,7 +1020,8 @@ def _find_imports(co):
(level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT)): (level_op[0] in hasconst or level_op[0] == LOAD_SMALL_INT)):
level = _get_const_value(level_op[0], level_op[1], consts) level = _get_const_value(level_op[0], level_op[1], consts)
fromlist = _get_const_value(from_op[0], from_op[1], consts) fromlist = _get_const_value(from_op[0], from_op[1], consts)
yield (names[oparg], level, fromlist) # IMPORT_NAME encodes lazy/eager flags in bits 0-1, name index in bits 2+
yield (names[oparg >> 2], level, fromlist)
def _find_store_names(co): def _find_store_names(co):
"""Find names of variables which are written in the code """Find names of variables which are written in the code

View file

@ -42,6 +42,11 @@ def make_pat():
]) + ]) +
r"))" r"))"
) )
lazy_softkw = (
r"^[ \t]*" + # at beginning of line + possible indentation
r"(?P<LAZY_SOFTKW>lazy)" +
r"(?=[ \t]+(?:import|from)\b)" # followed by 'import' or 'from'
)
builtinlist = [str(name) for name in dir(builtins) builtinlist = [str(name) for name in dir(builtins)
if not name.startswith('_') and if not name.startswith('_') and
name not in keyword.kwlist] name not in keyword.kwlist]
@ -56,7 +61,7 @@ def make_pat():
prog = re.compile("|".join([ prog = re.compile("|".join([
builtin, comment, string, kw, builtin, comment, string, kw,
match_softkw, case_default, match_softkw, case_default,
case_softkw_and_pattern, case_softkw_and_pattern, lazy_softkw,
any("SYNC", [r"\n"]), any("SYNC", [r"\n"]),
]), ]),
re.DOTALL | re.MULTILINE) re.DOTALL | re.MULTILINE)
@ -70,6 +75,7 @@ def make_pat():
"CASE_SOFTKW": "KEYWORD", "CASE_SOFTKW": "KEYWORD",
"CASE_DEFAULT_UNDERSCORE": "KEYWORD", "CASE_DEFAULT_UNDERSCORE": "KEYWORD",
"CASE_SOFTKW2": "KEYWORD", "CASE_SOFTKW2": "KEYWORD",
"LAZY_SOFTKW": "KEYWORD",
} }

View file

@ -542,6 +542,22 @@ def test_case_soft_keyword(self):
self._assert_highlighting('case _:', {'KEYWORD': [('1.0', '1.4'), self._assert_highlighting('case _:', {'KEYWORD': [('1.0', '1.4'),
('1.5', '1.6')]}) ('1.5', '1.6')]})
def test_lazy_soft_keyword(self):
# lazy followed by import
self._assert_highlighting('lazy import foo',
{'KEYWORD': [('1.0', '1.4'), ('1.5', '1.11')]})
self._assert_highlighting(' lazy import foo',
{'KEYWORD': [('1.4', '1.8'), ('1.9', '1.15')]})
# lazy followed by from
self._assert_highlighting('lazy from foo import bar',
{'KEYWORD': [('1.0', '1.4'), ('1.5', '1.9'),
('1.14', '1.20')]})
# lazy not followed by import/from (not highlighted)
self._assert_highlighting('lazy = 1', {})
self._assert_highlighting('lazy foo', {})
def test_long_multiline_string(self): def test_long_multiline_string(self):
source = textwrap.dedent('''\ source = textwrap.dedent('''\
"""a """a

View file

@ -1359,6 +1359,12 @@ def _find_and_load_unlocked(name, import_):
except AttributeError: except AttributeError:
msg = f"Cannot set an attribute on {parent!r} for child module {child!r}" msg = f"Cannot set an attribute on {parent!r} for child module {child!r}"
_warnings.warn(msg, ImportWarning) _warnings.warn(msg, ImportWarning)
# Set attributes to lazy submodules on the module.
try:
_imp._set_lazy_attributes(module, name)
except Exception as e:
msg = f"Cannot set lazy attributes on {name!r}: {e!r}"
_warnings.warn(msg, ImportWarning)
return module return module

1
Lib/keyword.py generated
View file

@ -56,6 +56,7 @@ kwlist = [
softkwlist = [ softkwlist = [
'_', '_',
'case', 'case',
'lazy',
'match', 'match',
'type' 'type'
] ]

View file

@ -36,6 +36,7 @@
import re import re
import __main__ import __main__
import warnings import warnings
import types
__all__ = ["Completer"] __all__ = ["Completer"]
@ -188,7 +189,16 @@ def attr_matches(self, text):
# property method, which is not desirable. # property method, which is not desirable.
matches.append(match) matches.append(match)
continue continue
if (value := getattr(thisobject, word, None)) is not None:
if (isinstance(thisobject, types.ModuleType)
and
isinstance(thisobject.__dict__.get(word), types.LazyImportType)
):
value = thisobject.__dict__.get(word)
else:
value = getattr(thisobject, word, None)
if value is not None:
matches.append(self._callable_postfix(value, match)) matches.append(self._callable_postfix(value, match))
else: else:
matches.append(match) matches.append(match)

View file

@ -13,6 +13,9 @@ extend-exclude = [
# New grammar constructions may not yet be recognized by Ruff, # New grammar constructions may not yet be recognized by Ruff,
# and tests re-use the same names as only the grammar is being checked. # and tests re-use the same names as only the grammar is being checked.
"test_grammar.py", "test_grammar.py",
# Lazy import syntax (PEP 810) not yet supported by Ruff
"test_import/data/lazy_imports/*.py",
"test_import/data/lazy_imports/**/*.py",
] ]
[per-file-target-version] [per-file-target-version]

View file

@ -14,6 +14,7 @@ BaseException
├── EOFError ├── EOFError
├── ExceptionGroup [BaseExceptionGroup] ├── ExceptionGroup [BaseExceptionGroup]
├── ImportError ├── ImportError
│ └── ImportCycleError
│ └── ModuleNotFoundError │ └── ModuleNotFoundError
├── LookupError ├── LookupError
│ ├── IndexError │ ├── IndexError

View file

@ -69,10 +69,14 @@ Module(body=[Try(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='ex
Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[]) Module(body=[TryStar(body=[Pass()], handlers=[ExceptHandler(type=Name(...), name='exc', body=[Pass(...)])], orelse=[Pass()], finalbody=[Pass()])], type_ignores=[])
Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=None)], type_ignores=[]) Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=None)], type_ignores=[])
Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=Constant(value='message', kind=None))], type_ignores=[]) Module(body=[Assert(test=Name(id='v', ctx=Load(...)), msg=Constant(value='message', kind=None))], type_ignores=[])
Module(body=[Import(names=[alias(name='sys', asname=None)])], type_ignores=[]) Module(body=[Import(names=[alias(name='sys', asname=None)], is_lazy=0)], type_ignores=[])
Module(body=[Import(names=[alias(name='foo', asname='bar')])], type_ignores=[]) Module(body=[Import(names=[alias(name='foo', asname='bar')], is_lazy=0)], type_ignores=[])
Module(body=[ImportFrom(module='sys', names=[alias(name='x', asname='y')], level=0)], type_ignores=[]) Module(body=[ImportFrom(module='sys', names=[alias(name='x', asname='y')], level=0, is_lazy=0)], type_ignores=[])
Module(body=[ImportFrom(module='sys', names=[alias(name='v', asname=None)], level=0)], type_ignores=[]) Module(body=[ImportFrom(module='sys', names=[alias(name='v', asname=None)], level=0, is_lazy=0)], type_ignores=[])
Module(body=[Import(names=[alias(name='sys', asname=None)], is_lazy=1)], type_ignores=[])
Module(body=[Import(names=[alias(name='foo', asname='bar')], is_lazy=1)], type_ignores=[])
Module(body=[ImportFrom(module='sys', names=[alias(name='x', asname='y')], level=0, is_lazy=1)], type_ignores=[])
Module(body=[ImportFrom(module='sys', names=[alias(name='v', asname=None)], level=0, is_lazy=1)], type_ignores=[])
Module(body=[Global(names=['v'])], type_ignores=[]) Module(body=[Global(names=['v'])], type_ignores=[])
Module(body=[Expr(value=Constant(value=1, kind=None))], type_ignores=[]) Module(body=[Expr(value=Constant(value=1, kind=None))], type_ignores=[])
Module(body=[Pass()], type_ignores=[]) Module(body=[Pass()], type_ignores=[])

View file

@ -118,6 +118,12 @@
# ImportFrom # ImportFrom
"from sys import x as y", "from sys import x as y",
"from sys import v", "from sys import v",
# Lazy Import
"lazy import sys",
"lazy import foo as bar",
# Lazy ImportFrom
"lazy from sys import x as y",
"lazy from sys import v",
# Global # Global
"global v", "global v",
# Expr # Expr
@ -460,10 +466,14 @@ def main():
('Module', [('TryStar', (1, 0, 7, 6), [('Pass', (2, 2, 2, 6))], [('ExceptHandler', (3, 0, 4, 6), ('Name', (3, 8, 3, 17), 'Exception', ('Load',)), 'exc', [('Pass', (4, 2, 4, 6))])], [('Pass', (5, 7, 5, 11))], [('Pass', (7, 2, 7, 6))])], []), ('Module', [('TryStar', (1, 0, 7, 6), [('Pass', (2, 2, 2, 6))], [('ExceptHandler', (3, 0, 4, 6), ('Name', (3, 8, 3, 17), 'Exception', ('Load',)), 'exc', [('Pass', (4, 2, 4, 6))])], [('Pass', (5, 7, 5, 11))], [('Pass', (7, 2, 7, 6))])], []),
('Module', [('Assert', (1, 0, 1, 8), ('Name', (1, 7, 1, 8), 'v', ('Load',)), None)], []), ('Module', [('Assert', (1, 0, 1, 8), ('Name', (1, 7, 1, 8), 'v', ('Load',)), None)], []),
('Module', [('Assert', (1, 0, 1, 19), ('Name', (1, 7, 1, 8), 'v', ('Load',)), ('Constant', (1, 10, 1, 19), 'message', None))], []), ('Module', [('Assert', (1, 0, 1, 19), ('Name', (1, 7, 1, 8), 'v', ('Load',)), ('Constant', (1, 10, 1, 19), 'message', None))], []),
('Module', [('Import', (1, 0, 1, 10), [('alias', (1, 7, 1, 10), 'sys', None)])], []), ('Module', [('Import', (1, 0, 1, 10), [('alias', (1, 7, 1, 10), 'sys', None)], 0)], []),
('Module', [('Import', (1, 0, 1, 17), [('alias', (1, 7, 1, 17), 'foo', 'bar')])], []), ('Module', [('Import', (1, 0, 1, 17), [('alias', (1, 7, 1, 17), 'foo', 'bar')], 0)], []),
('Module', [('ImportFrom', (1, 0, 1, 22), 'sys', [('alias', (1, 16, 1, 22), 'x', 'y')], 0)], []), ('Module', [('ImportFrom', (1, 0, 1, 22), 'sys', [('alias', (1, 16, 1, 22), 'x', 'y')], 0, 0)], []),
('Module', [('ImportFrom', (1, 0, 1, 17), 'sys', [('alias', (1, 16, 1, 17), 'v', None)], 0)], []), ('Module', [('ImportFrom', (1, 0, 1, 17), 'sys', [('alias', (1, 16, 1, 17), 'v', None)], 0, 0)], []),
('Module', [('Import', (1, 0, 1, 15), [('alias', (1, 12, 1, 15), 'sys', None)], 1)], []),
('Module', [('Import', (1, 0, 1, 22), [('alias', (1, 12, 1, 22), 'foo', 'bar')], 1)], []),
('Module', [('ImportFrom', (1, 0, 1, 27), 'sys', [('alias', (1, 21, 1, 27), 'x', 'y')], 0, 1)], []),
('Module', [('ImportFrom', (1, 0, 1, 22), 'sys', [('alias', (1, 21, 1, 22), 'v', None)], 0, 1)], []),
('Module', [('Global', (1, 0, 1, 8), ['v'])], []), ('Module', [('Global', (1, 0, 1, 8), ['v'])], []),
('Module', [('Expr', (1, 0, 1, 1), ('Constant', (1, 0, 1, 1), 1, None))], []), ('Module', [('Expr', (1, 0, 1, 1), ('Constant', (1, 0, 1, 1), 1, None))], []),
('Module', [('Pass', (1, 0, 1, 4))], []), ('Module', [('Pass', (1, 0, 1, 4))], []),

View file

@ -1692,8 +1692,8 @@ def check_text(code, empty, full, **kwargs):
check_text( check_text(
"import _ast as ast; from module import sub", "import _ast as ast; from module import sub",
empty="Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)])", empty="Module(body=[Import(names=[alias(name='_ast', asname='ast')], is_lazy=0), ImportFrom(module='module', names=[alias(name='sub')], level=0, is_lazy=0)])",
full="Module(body=[Import(names=[alias(name='_ast', asname='ast')]), ImportFrom(module='module', names=[alias(name='sub')], level=0)], type_ignores=[])", full="Module(body=[Import(names=[alias(name='_ast', asname='ast')], is_lazy=0), ImportFrom(module='module', names=[alias(name='sub')], level=0, is_lazy=0)], type_ignores=[])",
) )
def test_copy_location(self): def test_copy_location(self):

View file

@ -62,6 +62,7 @@ def test_config_get(self):
("int_max_str_digits", int, None), ("int_max_str_digits", int, None),
("interactive", bool, None), ("interactive", bool, None),
("isolated", bool, None), ("isolated", bool, None),
("lazy_imports", int, None),
("malloc_stats", bool, None), ("malloc_stats", bool, None),
("module_search_paths", list[str], "path"), ("module_search_paths", list[str], "path"),
("optimization_level", int, None), ("optimization_level", int, None),

View file

@ -292,7 +292,7 @@ def wrap_func_w_kwargs():
1 LOAD_SMALL_INT 0 1 LOAD_SMALL_INT 0
LOAD_CONST 1 (('*',)) LOAD_CONST 1 (('*',))
IMPORT_NAME 0 (math) IMPORT_NAME 2 (math + eager)
CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR) CALL_INTRINSIC_1 2 (INTRINSIC_IMPORT_STAR)
POP_TOP POP_TOP
LOAD_CONST 2 (None) LOAD_CONST 2 (None)

View file

@ -635,6 +635,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'tracemalloc': 0, 'tracemalloc': 0,
'perf_profiling': 0, 'perf_profiling': 0,
'import_time': 0, 'import_time': 0,
'lazy_imports': -1,
'thread_inherit_context': DEFAULT_THREAD_INHERIT_CONTEXT, 'thread_inherit_context': DEFAULT_THREAD_INHERIT_CONTEXT,
'context_aware_warnings': DEFAULT_CONTEXT_AWARE_WARNINGS, 'context_aware_warnings': DEFAULT_CONTEXT_AWARE_WARNINGS,
'code_debug_ranges': True, 'code_debug_ranges': True,

View file

@ -0,0 +1,4 @@
def f():
pass
x = 42

View file

@ -0,0 +1,2 @@
__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2']
import test.test_import.data.lazy_imports.basic2

View file

@ -0,0 +1,2 @@
__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2']
lazy from .basic2 import f

View file

@ -0,0 +1,3 @@
__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2']
import test.test_import.data.lazy_imports.basic2
test.test_import.data.lazy_imports.basic2.f()

View file

@ -0,0 +1,2 @@
lazy import test.test_import.data.lazy_imports.basic2
x = dir()

View file

@ -0,0 +1 @@
lazy from test.test_import.data.lazy_imports import basic2

View file

@ -0,0 +1 @@
lazy import test.test_import.data.lazy_imports.basic2

View file

@ -0,0 +1,3 @@
lazy import test.test_import.data.lazy_imports.basic2 as basic2
basic2.f()

View file

@ -0,0 +1,3 @@
# Module that exists but doesn't have expected attributes
x = 42
# No 'nonexistent_attr' here

View file

@ -0,0 +1,2 @@
# Module that raises an error during import
raise ValueError("This module always fails to import")

View file

@ -0,0 +1,5 @@
__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2']
def f():
import test.test_import.data.lazy_imports.basic2
f()

View file

@ -0,0 +1,5 @@
__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2']
try:
import test.test_import.data.lazy_imports.basic2
except:
pass

View file

@ -0,0 +1 @@
basic = __lazy_import__('test.test_import.data.lazy_imports.basic2')

View file

@ -0,0 +1,13 @@
import sys
def myimport(*args):
return sys.modules[__name__]
new_globals = dict(globals())
new_globals["__builtins__"] = {
"__import__": myimport,
}
basic2 = 42
basic = __lazy_import__("test.test_import.data.lazy_imports", fromlist="basic2", globals=new_globals)
basic

View file

@ -0,0 +1,2 @@
basic = __lazy_import__('test.test_import.data.lazy_imports', fromlist="basic2")
basic

View file

@ -0,0 +1,3 @@
def f():
import test.test_import.data.lazy_imports.basic2 as basic2
return basic2

View file

@ -0,0 +1,10 @@
import sys
def filter(module_name, imported_name, from_list):
assert module_name == __name__
assert imported_name == "test.test_import.data.lazy_imports.basic2"
return False
sys.set_lazy_imports_filter(filter)
lazy import test.test_import.data.lazy_imports.basic2 as basic2

View file

@ -0,0 +1,11 @@
import importlib
def filter(module_name, imported_name, from_list):
assert module_name == __name__
assert imported_name == "test.test_import.data.lazy_imports.basic2"
assert from_list == ['f']
return False
importlib.set_lazy_imports(None, filter)
lazy from import test.test_import.data.lazy_imports.basic2 import f

View file

@ -0,0 +1,11 @@
import importlib
def filter(module_name, imported_name, from_list):
assert module_name == __name__
assert imported_name == "test.test_import.data.lazy_imports.basic2"
assert from_list == ['f']
return True
importlib.set_lazy_imports(None, filter)
lazy from import test.test_import.data.lazy_imports.basic2 import f

View file

@ -0,0 +1,11 @@
import sys
def filter(module_name, imported_name, from_list):
assert module_name == __name__
assert imported_name == "test.test_import.data.lazy_imports.basic2"
return True
sys.set_lazy_imports("normal")
sys.set_lazy_imports_filter(filter)
lazy import test.test_import.data.lazy_imports.basic2 as basic2

View file

@ -0,0 +1,5 @@
import sys
sys.set_lazy_imports("none")
lazy import test.test_import.data.lazy_imports.basic2 as basic2

View file

@ -0,0 +1,5 @@
import sys
sys.set_lazy_imports("all")
import test.test_import.data.lazy_imports.basic2 as basic2

View file

@ -0,0 +1,9 @@
# Test that globals() returns lazy proxy objects without reifying
lazy import test.test_import.data.lazy_imports.basic2 as basic2
def get_from_globals():
g = globals()
return g['basic2']
def get_direct():
return basic2

View file

@ -0,0 +1,3 @@
# SyntaxError: lazy import inside class body is not allowed
class Foo:
lazy import json

View file

@ -0,0 +1,6 @@
# Test __lazy_modules__ with from imports
__lazy_modules__ = ['test.test_import.data.lazy_imports.basic2']
from test.test_import.data.lazy_imports.basic2 import x, f
def get_x():
return x

View file

@ -0,0 +1 @@
lazy from __future__ import annotations

View file

@ -0,0 +1,7 @@
lazy import test.test_import.data.lazy_imports.basic2 as basic2
def f():
x = globals()
return x['basic2'].resolve()
f()

View file

@ -0,0 +1,2 @@
def f():
lazy import foo

View file

@ -0,0 +1,2 @@
lazy import test.test_import.data.lazy_imports.pkg.bar
x = test.test_import.data.lazy_imports.pkg.bar.f

View file

@ -0,0 +1,4 @@
try:
lazy import foo
except:
pass

View file

@ -0,0 +1,4 @@
try:
lazy from foo import bar
except:
pass

View file

@ -0,0 +1 @@
lazy from foo import *

View file

@ -0,0 +1,3 @@
import contextlib
with contextlib.nullcontext():
lazy import test.test_import.data.lazy_imports.basic2

View file

@ -0,0 +1,3 @@
import contextlib
with contextlib.nullcontext():
lazy import test.test_import.data.lazy_imports.basic2 as basic2

View file

@ -0,0 +1,5 @@
lazy import test.test_import.data.lazy_imports.basic2 as basic2
import sys
mod = sys.modules[__name__]
x = mod.__dict__

View file

@ -0,0 +1,5 @@
lazy import test.test_import.data.lazy_imports.basic2 as basic2
import sys
mod = sys.modules[__name__]
x = mod.basic2

View file

@ -0,0 +1,5 @@
lazy import test.test_import.data.lazy_imports.basic2 as basic2
import sys
mod = sys.modules[__name__]
x = mod.__name__

View file

@ -0,0 +1,5 @@
# Test that lazy from import with multiple names only reifies accessed names
lazy from test.test_import.data.lazy_imports.basic2 import f, x
def get_globals():
return globals()

View file

@ -0,0 +1 @@
x = 42

View file

@ -0,0 +1,2 @@
def foo():
return 'foo'

View file

@ -0,0 +1 @@
def f(): pass

View file

@ -0,0 +1,4 @@
lazy from . import b, x
def get_globals():
return globals()

View file

@ -0,0 +1,5 @@
# Test relative imports with lazy keyword
lazy from . import basic2
def get_basic2():
return basic2

View file

@ -0,0 +1,8 @@
# Test relative from imports with lazy keyword
lazy from .basic2 import x, f
def get_x():
return x
def get_f():
return f

View file

@ -0,0 +1,4 @@
try:
import test.test_import.data.lazy_imports.basic2
except:
pass

View file

@ -0,0 +1,4 @@
try:
from test.test_import.data.lazy_imports.basic2 import f
except:
pass

File diff suppressed because it is too large Load diff

View file

@ -89,10 +89,14 @@ def test_gen_colors_keyword_highlighting(self):
("obj.list", [(".", "op")]), ("obj.list", [(".", "op")]),
("obj.match", [(".", "op")]), ("obj.match", [(".", "op")]),
("b. \\\n format", [(".", "op")]), ("b. \\\n format", [(".", "op")]),
("lazy", []),
("lazy()", [('(', 'op'), (')', 'op')]),
# highlights # highlights
("set", [("set", "builtin")]), ("set", [("set", "builtin")]),
("list", [("list", "builtin")]), ("list", [("list", "builtin")]),
(" \n dict", [("dict", "builtin")]), (" \n dict", [("dict", "builtin")]),
(" lazy import", [("lazy", "soft_keyword"), ("import", "keyword")]),
("lazy from cool_people import pablo", [('lazy', 'soft_keyword'), ('from', 'keyword'), ('import', 'keyword')])
] ]
for code, expected_highlights in cases: for code, expected_highlights in cases:
with self.subTest(code=code): with self.subTest(code=code):

View file

@ -3461,6 +3461,119 @@ def test_ifexp_body_stmt_else_stmt(self):
]: ]:
self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg) self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg)
class LazyImportRestrictionTestCase(SyntaxErrorTestCase):
"""Test syntax restrictions for lazy imports."""
def test_lazy_import_in_try_block(self):
"""Test that lazy imports are not allowed inside try blocks."""
self._check_error("""\
try:
lazy import os
except:
pass
""", "lazy import not allowed inside try/except blocks")
self._check_error("""\
try:
lazy from sys import path
except ImportError:
pass
""", "lazy from ... import not allowed inside try/except blocks")
def test_lazy_import_in_trystar_block(self):
"""Test that lazy imports are not allowed inside try* blocks."""
self._check_error("""\
try:
lazy import json
except* Exception:
pass
""", "lazy import not allowed inside try/except blocks")
self._check_error("""\
try:
lazy from collections import defaultdict
except* ImportError:
pass
""", "lazy from ... import not allowed inside try/except blocks")
def test_lazy_import_in_function(self):
"""Test that lazy imports are not allowed inside functions."""
self._check_error("""\
def func():
lazy import math
""", "lazy import not allowed inside functions")
self._check_error("""\
def func():
lazy from datetime import datetime
""", "lazy from ... import not allowed inside functions")
def test_lazy_import_in_async_function(self):
"""Test that lazy imports are not allowed inside async functions."""
self._check_error("""\
async def async_func():
lazy import asyncio
""", "lazy import not allowed inside functions")
self._check_error("""\
async def async_func():
lazy from json import loads
""", "lazy from ... import not allowed inside functions")
def test_lazy_import_in_class(self):
"""Test that lazy imports are not allowed inside classes."""
self._check_error("""\
class MyClass:
lazy import typing
""", "lazy import not allowed inside classes")
self._check_error("""\
class MyClass:
lazy from abc import ABC
""", "lazy from ... import not allowed inside classes")
def test_lazy_import_star_forbidden(self):
"""Test that 'lazy from ... import *' is forbidden everywhere."""
# At module level should also be forbidden
self._check_error("lazy from os import *",
"lazy from ... import \\* is not allowed")
# Inside function should give lazy function error first
self._check_error("""\
def func():
lazy from sys import *
""", "lazy from ... import not allowed inside functions")
def test_lazy_import_nested_scopes(self):
"""Test lazy imports in nested scopes."""
self._check_error("""\
class Outer:
def method(self):
lazy import sys
""", "lazy import not allowed inside functions")
self._check_error("""\
def outer():
class Inner:
lazy import json
""", "lazy import not allowed inside classes")
self._check_error("""\
def outer():
def inner():
lazy from collections import deque
""", "lazy from ... import not allowed inside functions")
def test_lazy_import_valid_cases(self):
"""Test that lazy imports work at module level."""
# These should compile without errors
compile("lazy import os", "<test>", "exec")
compile("lazy from sys import path", "<test>", "exec")
compile("lazy import json as j", "<test>", "exec")
compile("lazy from datetime import datetime as dt", "<test>", "exec")
def load_tests(loader, tests, pattern): def load_tests(loader, tests, pattern):
tests.addTest(doctest.DocTestSuite()) tests.addTest(doctest.DocTestSuite())
return tests return tests

View file

@ -866,7 +866,8 @@ def test_sys_flags(self):
"dont_write_bytecode", "no_user_site", "no_site", "dont_write_bytecode", "no_user_site", "no_site",
"ignore_environment", "verbose", "bytes_warning", "quiet", "ignore_environment", "verbose", "bytes_warning", "quiet",
"hash_randomization", "isolated", "dev_mode", "utf8_mode", "hash_randomization", "isolated", "dev_mode", "utf8_mode",
"warn_default_encoding", "safe_path", "int_max_str_digits") "warn_default_encoding", "safe_path", "int_max_str_digits",
"lazy_imports")
for attr in attrs: for attr in attrs:
self.assertHasAttr(sys.flags, attr) self.assertHasAttr(sys.flags, attr)
attr_type = bool if attr in ("dev_mode", "safe_path") else int attr_type = bool if attr in ("dev_mode", "safe_path") else int
@ -1726,7 +1727,7 @@ def get_gen(): yield 1
md_gil = '?' md_gil = '?'
else: else:
md_gil = '' md_gil = ''
check(unittest, size('PPPP?' + md_gil + 'NPPPPP')) check(unittest, size('PPPPI?' + md_gil + 'NPPPPP'))
# None # None
check(None, size('')) check(None, size(''))
# NotImplementedType # NotImplementedType

View file

@ -55,7 +55,7 @@ def test_names(self):
'CoroutineType', 'EllipsisType', 'FrameType', 'FunctionType', 'CoroutineType', 'EllipsisType', 'FrameType', 'FunctionType',
'FrameLocalsProxyType', 'FrameLocalsProxyType',
'GeneratorType', 'GenericAlias', 'GetSetDescriptorType', 'GeneratorType', 'GenericAlias', 'GetSetDescriptorType',
'LambdaType', 'MappingProxyType', 'MemberDescriptorType', 'LambdaType', 'LazyImportType', 'MappingProxyType', 'MemberDescriptorType',
'MethodDescriptorType', 'MethodType', 'MethodWrapperType', 'MethodDescriptorType', 'MethodType', 'MethodWrapperType',
'ModuleType', 'NoneType', 'NotImplementedType', 'SimpleNamespace', 'ModuleType', 'NoneType', 'NotImplementedType', 'SimpleNamespace',
'TracebackType', 'UnionType', 'WrapperDescriptorType', 'TracebackType', 'UnionType', 'WrapperDescriptorType',

View file

@ -76,6 +76,10 @@ def _m(self): pass
# CapsuleType cannot be accessed from pure Python, # CapsuleType cannot be accessed from pure Python,
# so there is no fallback definition. # so there is no fallback definition.
exec("lazy import sys as _lazy_sys", _lz := {})
LazyImportType = type(_lz['_lazy_sys'])
del _lz
del sys, _f, _g, _C, _c, _ag, _cell_factory # Not for export del sys, _f, _g, _C, _c, _ag, _cell_factory # Not for export

View file

@ -539,6 +539,7 @@ OBJECT_OBJS= \
Objects/funcobject.o \ Objects/funcobject.o \
Objects/interpolationobject.o \ Objects/interpolationobject.o \
Objects/iterobject.o \ Objects/iterobject.o \
Objects/lazyimportobject.o \
Objects/listobject.o \ Objects/listobject.o \
Objects/longobject.o \ Objects/longobject.o \
Objects/dictobject.o \ Objects/dictobject.o \
@ -1372,6 +1373,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_interpolation.h \ $(srcdir)/Include/internal/pycore_interpolation.h \
$(srcdir)/Include/internal/pycore_intrinsics.h \ $(srcdir)/Include/internal/pycore_intrinsics.h \
$(srcdir)/Include/internal/pycore_jit.h \ $(srcdir)/Include/internal/pycore_jit.h \
$(srcdir)/Include/internal/pycore_lazyimportobject.h \
$(srcdir)/Include/internal/pycore_list.h \ $(srcdir)/Include/internal/pycore_list.h \
$(srcdir)/Include/internal/pycore_llist.h \ $(srcdir)/Include/internal/pycore_llist.h \
$(srcdir)/Include/internal/pycore_lock.h \ $(srcdir)/Include/internal/pycore_lock.h \
@ -2652,6 +2654,8 @@ TESTSUBDIRS= idlelib/idle_test \
test/test_import/data/package3 \ test/test_import/data/package3 \
test/test_import/data/package4 \ test/test_import/data/package4 \
test/test_import/data/unwritable \ test/test_import/data/unwritable \
test/test_import/data/lazy_imports \
test/test_import/data/lazy_imports/pkg \
test/test_importlib \ test/test_importlib \
test/test_importlib/builtin \ test/test_importlib/builtin \
test/test_importlib/extension \ test/test_importlib/extension \

View file

@ -0,0 +1 @@
Implement :pep:`810`. Patch by Pablo Galindo and Dino Viehland.

View file

@ -2,6 +2,7 @@
#include "Python.h" #include "Python.h"
#include "pycore_descrobject.h" // _PyMethodWrapper_Type #include "pycore_descrobject.h" // _PyMethodWrapper_Type
#include "pycore_lazyimportobject.h" // PyLazyImport_Type
#include "pycore_namespace.h" // _PyNamespace_Type #include "pycore_namespace.h" // _PyNamespace_Type
#include "pycore_object.h" // _PyNone_Type, _PyNotImplemented_Type #include "pycore_object.h" // _PyNone_Type, _PyNotImplemented_Type
#include "pycore_unionobject.h" // _PyUnion_Type #include "pycore_unionobject.h" // _PyUnion_Type
@ -35,6 +36,7 @@ _types_exec(PyObject *m)
EXPORT_STATIC_TYPE("GetSetDescriptorType", PyGetSetDescr_Type); EXPORT_STATIC_TYPE("GetSetDescriptorType", PyGetSetDescr_Type);
// LambdaType is the same as FunctionType // LambdaType is the same as FunctionType
EXPORT_STATIC_TYPE("LambdaType", PyFunction_Type); EXPORT_STATIC_TYPE("LambdaType", PyFunction_Type);
EXPORT_STATIC_TYPE("LazyImportType", PyLazyImport_Type);
EXPORT_STATIC_TYPE("MappingProxyType", PyDictProxy_Type); EXPORT_STATIC_TYPE("MappingProxyType", PyDictProxy_Type);
EXPORT_STATIC_TYPE("MemberDescriptorType", PyMemberDescr_Type); EXPORT_STATIC_TYPE("MemberDescriptorType", PyMemberDescr_Type);
EXPORT_STATIC_TYPE("MethodDescriptorType", PyMethodDescr_Type); EXPORT_STATIC_TYPE("MethodDescriptorType", PyMethodDescr_Type);

View file

@ -4694,6 +4694,14 @@ _PyDict_SizeOf_LockHeld(PyDictObject *mp)
return (Py_ssize_t)res; return (Py_ssize_t)res;
} }
void
_PyDict_ClearKeysVersion(PyObject *mp)
{
ASSERT_DICT_LOCKED(mp);
FT_ATOMIC_STORE_UINT32_RELAXED(((PyDictObject *)mp)->ma_keys->dk_version, 0);
}
Py_ssize_t Py_ssize_t
_PyDict_SizeOf(PyDictObject *mp) _PyDict_SizeOf(PyDictObject *mp)
{ {

View file

@ -2022,6 +2022,12 @@ static PyTypeObject _PyExc_ImportError = {
}; };
PyObject *PyExc_ImportError = (PyObject *)&_PyExc_ImportError; PyObject *PyExc_ImportError = (PyObject *)&_PyExc_ImportError;
/*
* ImportCycleError extends ImportError
*/
MiddlingExtendsException(PyExc_ImportError, ImportCycleError, ImportError,
"Import produces a cycle.");
/* /*
* ModuleNotFoundError extends ImportError * ModuleNotFoundError extends ImportError
*/ */
@ -4455,6 +4461,7 @@ static struct static_exception static_exceptions[] = {
{&_PyExc_IncompleteInputError, "_IncompleteInputError"}, // base: SyntaxError(Exception) {&_PyExc_IncompleteInputError, "_IncompleteInputError"}, // base: SyntaxError(Exception)
ITEM(IndexError), // base: LookupError(Exception) ITEM(IndexError), // base: LookupError(Exception)
ITEM(KeyError), // base: LookupError(Exception) ITEM(KeyError), // base: LookupError(Exception)
ITEM(ImportCycleError), // base: ImportError(Exception)
ITEM(ModuleNotFoundError), // base: ImportError(Exception) ITEM(ModuleNotFoundError), // base: ImportError(Exception)
ITEM(NotImplementedError), // base: RuntimeError(Exception) ITEM(NotImplementedError), // base: RuntimeError(Exception)
ITEM(PythonFinalizationError), // base: RuntimeError(Exception) ITEM(PythonFinalizationError), // base: RuntimeError(Exception)
@ -4650,4 +4657,3 @@ _PyException_AddNote(PyObject *exc, PyObject *note)
Py_XDECREF(r); Py_XDECREF(r);
return res; return res;
} }

Some files were not shown because too many files have changed in this diff Show more