937 lines
79 KiB
HTML
937 lines
79 KiB
HTML
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="color-scheme" content="light dark">
|
||
<title>PEP 690 – Lazy Imports | peps.python.org</title>
|
||
<link rel="shortcut icon" href="../_static/py.png">
|
||
<link rel="canonical" href="https://peps.python.org/pep-0690/">
|
||
<link rel="stylesheet" href="../_static/style.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
|
||
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
|
||
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
|
||
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
|
||
<meta property="og:title" content='PEP 690 – Lazy Imports | peps.python.org'>
|
||
<meta property="og:description" content="This PEP proposes a feature to transparently defer the finding and execution of imported modules until the moment when an imported object is first used. Since Python programs commonly import many more modules than a single invocation of the program is ...">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:url" content="https://peps.python.org/pep-0690/">
|
||
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
|
||
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
|
||
<meta property="og:image:alt" content="Python PEPs">
|
||
<meta property="og:image:width" content="200">
|
||
<meta property="og:image:height" content="200">
|
||
<meta name="description" content="This PEP proposes a feature to transparently defer the finding and execution of imported modules until the moment when an imported object is first used. Since Python programs commonly import many more modules than a single invocation of the program is ...">
|
||
<meta name="theme-color" content="#3776ab">
|
||
</head>
|
||
<body>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Following system colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="9"></circle>
|
||
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected dark colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
|
||
</svg>
|
||
</symbol>
|
||
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
|
||
<title>Selected light colour scheme</title>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||
</svg>
|
||
</symbol>
|
||
</svg>
|
||
<script>
|
||
|
||
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
|
||
</script>
|
||
<section id="pep-page-section">
|
||
<header>
|
||
<h1>Python Enhancement Proposals</h1>
|
||
<ul class="breadcrumbs">
|
||
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li>
|
||
<li><a href="../pep-0000/">PEP Index</a> » </li>
|
||
<li>PEP 690</li>
|
||
</ul>
|
||
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
|
||
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
|
||
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
|
||
</button>
|
||
</header>
|
||
<article>
|
||
<section id="pep-content">
|
||
<h1 class="page-title">PEP 690 – Lazy Imports</h1>
|
||
<dl class="rfc2822 field-list simple">
|
||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
||
<dd class="field-odd">Germán Méndez Bravo <german.mb at gmail.com>, Carl Meyer <carl at oddbird.net></dd>
|
||
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
|
||
<dd class="field-even">Barry Warsaw <barry at python.org></dd>
|
||
<dt class="field-odd">Discussions-To<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-690-lazy-imports/15474">Discourse thread</a></dd>
|
||
<dt class="field-even">Status<span class="colon">:</span></dt>
|
||
<dd class="field-even"><abbr title="Formally declined and will not be accepted">Rejected</abbr></dd>
|
||
<dt class="field-odd">Type<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
|
||
<dt class="field-even">Created<span class="colon">:</span></dt>
|
||
<dd class="field-even">29-Apr-2022</dd>
|
||
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
|
||
<dd class="field-odd">3.12</dd>
|
||
<dt class="field-even">Post-History<span class="colon">:</span></dt>
|
||
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-690-lazy-imports/15474" title="Discourse thread">03-May-2022</a>,
|
||
<a class="reference external" href="https://mail.python.org/archives/list/python-dev@python.org/thread/IHOSWMIBKCXVB46FI7NGOC2F34RUYZ5Z/" title="Python-Dev thread">03-May-2022</a></dd>
|
||
<dt class="field-odd">Resolution<span class="colon">:</span></dt>
|
||
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-690-lazy-imports-again/19661/26">Discourse message</a></dd>
|
||
</dl>
|
||
<hr class="docutils" />
|
||
<section id="contents">
|
||
<details><summary>Table of Contents</summary><ul class="simple">
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#motivation">Motivation</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#example">Example</a></li>
|
||
<li><a class="reference internal" href="#intended-usage">Intended usage</a></li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a></li>
|
||
<li><a class="reference internal" href="#debugging">Debugging</a></li>
|
||
<li><a class="reference internal" href="#per-module-opt-out">Per-module opt-out</a></li>
|
||
<li><a class="reference internal" href="#testing">Testing</a></li>
|
||
<li><a class="reference internal" href="#c-api">C API</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a><ul>
|
||
<li><a class="reference internal" href="#import-side-effects">Import Side Effects</a></li>
|
||
<li><a class="reference internal" href="#dynamic-paths">Dynamic Paths</a></li>
|
||
<li><a class="reference internal" href="#deferred-exceptions">Deferred Exceptions</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#drawbacks">Drawbacks</a></li>
|
||
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
|
||
<li><a class="reference internal" href="#performance-impact">Performance Impact</a></li>
|
||
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#wrapping-deferred-exceptions">Wrapping deferred exceptions</a></li>
|
||
<li><a class="reference internal" href="#per-module-opt-in">Per-module opt-in</a></li>
|
||
<li><a class="reference internal" href="#explicit-syntax-for-individual-lazy-imports">Explicit syntax for individual lazy imports</a></li>
|
||
<li><a class="reference internal" href="#environment-variable-to-enable-lazy-imports">Environment variable to enable lazy imports</a></li>
|
||
<li><a class="reference internal" href="#removing-the-l-flag">Removing the <code class="docutils literal notranslate"><span class="pre">-L</span></code> flag</a></li>
|
||
<li><a class="reference internal" href="#half-lazy-imports">Half-lazy imports</a></li>
|
||
<li><a class="reference internal" href="#lazy-dynamic-imports">Lazy dynamic imports</a></li>
|
||
<li><a class="reference internal" href="#deep-eager-imports-override">Deep eager-imports override</a></li>
|
||
<li><a class="reference internal" href="#making-lazy-imports-the-default-behavior">Making lazy imports the default behavior</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
</details></section>
|
||
<section id="abstract">
|
||
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
|
||
<p>This PEP proposes a feature to transparently defer the finding and execution of
|
||
imported modules until the moment when an imported object is first used. Since
|
||
Python programs commonly import many more modules than a single invocation of
|
||
the program is likely to use in practice, lazy imports can greatly reduce the
|
||
overall number of modules loaded, improving startup time and memory usage. Lazy
|
||
imports also mostly eliminate the risk of import cycles.</p>
|
||
</section>
|
||
<section id="motivation">
|
||
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
|
||
<p>Common Python code style <a class="pep reference internal" href="../pep-0008/#imports" title="PEP 8 – Style Guide for Python Code § Imports">prefers</a> imports at module
|
||
level, so they don’t have to be repeated within each scope the imported object
|
||
is used in, and to avoid the inefficiency of repeated execution of the import
|
||
system at runtime. This means that importing the main module of a program
|
||
typically results in an immediate cascade of imports of most or all of the
|
||
modules that may ever be needed by the program.</p>
|
||
<p>Consider the example of a Python command line program (CLI) with a number of
|
||
subcommands. Each subcommand may perform different tasks, requiring the import
|
||
of different dependencies. But a given invocation of the program will only
|
||
execute a single subcommand, or possibly none (i.e. if just <code class="docutils literal notranslate"><span class="pre">--help</span></code> usage
|
||
info is requested). Top-level eager imports in such a program will result in the
|
||
import of many modules that will never be used at all; the time spent (possibly
|
||
compiling and) executing these modules is pure waste.</p>
|
||
<p>To improve startup time, some large Python CLIs make imports lazy by manually
|
||
placing imports inline into functions to delay imports of expensive subsystems.
|
||
This manual approach is labor-intensive and fragile; one misplaced import or
|
||
refactor can easily undo painstaking optimization work.</p>
|
||
<p>The Python standard library already includes built-in support for lazy imports,
|
||
via <a class="reference external" href="https://docs.python.org/3/library/importlib.html#importlib.util.LazyLoader">importlib.util.LazyLoader</a>.
|
||
There are also third-party packages such as <a class="reference external" href="https://github.com/bwesterb/py-demandimport/">demandimport</a>. These provide a “lazy module
|
||
object” which delays its own import until first attribute access. This is not
|
||
sufficient to make all imports lazy: imports such as <code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">foo</span> <span class="pre">import</span> <span class="pre">a,</span> <span class="pre">b</span></code>
|
||
will still eagerly import the module <code class="docutils literal notranslate"><span class="pre">foo</span></code> since they immediately access an
|
||
attribute from it. It also imposes noticeable runtime overhead on every module
|
||
attribute access, since it requires a Python-level <code class="docutils literal notranslate"><span class="pre">__getattr__</span></code> or
|
||
<code class="docutils literal notranslate"><span class="pre">__getattribute__</span></code> implementation.</p>
|
||
<p>Authors of scientific Python packages have also made extensive use of lazy
|
||
imports to allow users to write e.g. <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">scipy</span> <span class="pre">as</span> <span class="pre">sp</span></code> and then easily
|
||
access many different submodules with e.g. <code class="docutils literal notranslate"><span class="pre">sp.linalg</span></code>, without requiring all
|
||
the many submodules to be imported up-front. <a class="reference external" href="https://scientific-python.org/specs/spec-0001/">SPEC 1</a> codifies this practice in the
|
||
form of a <code class="docutils literal notranslate"><span class="pre">lazy_loader</span></code> library that can be used explicitly in a package
|
||
<code class="docutils literal notranslate"><span class="pre">__init__.py</span></code> to provide lazily accessible submodules.</p>
|
||
<p>Users of static typing also have to import names for use in type annotations
|
||
that may never be used at runtime (if <a class="pep reference internal" href="../pep-0563/" title="PEP 563 – Postponed Evaluation of Annotations">PEP 563</a> or possibly in future
|
||
<a class="pep reference internal" href="../pep-0649/" title="PEP 649 – Deferred Evaluation Of Annotations Using Descriptors">PEP 649</a> are used to avoid eager runtime evaluation of annotations). Lazy
|
||
imports are very attractive in this scenario to avoid overhead of unneeded
|
||
imports.</p>
|
||
<p>This PEP proposes a more general and comprehensive solution for lazy imports
|
||
that can encompass all of the above use cases and does not impose detectable
|
||
overhead in real-world use. The implementation in this PEP has already
|
||
<a class="reference external" href="https://github.com/facebookincubator/cinder/blob/cinder/3.8/CinderDoc/lazy_imports.rst">demonstrated</a>
|
||
startup time improvements up to 70% and memory-use reductions up to 40% on
|
||
real-world Python CLIs.</p>
|
||
<p>Lazy imports also eliminate most import cycles. With eager imports, “false
|
||
cycles” can easily occur which are fixed by simply moving an import to the
|
||
bottom of a module or inline into a function, or switching from <code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">foo</span>
|
||
<span class="pre">import</span> <span class="pre">bar</span></code> to <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">foo</span></code>. With lazy imports, these “cycles” just work.
|
||
The only cycles which will remain are those where two modules actually each use
|
||
a name from the other at module level; these “true” cycles are only fixable by
|
||
refactoring the classes or functions involved.</p>
|
||
</section>
|
||
<section id="rationale">
|
||
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
|
||
<p>The aim of this feature is to make imports transparently lazy. “Lazy” means
|
||
that the import of a module (execution of the module body and addition of the
|
||
module object to <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code>) should not occur until the module (or a name
|
||
imported from it) is actually referenced during execution. “Transparent” means
|
||
that besides the delayed import (and necessarily observable effects of that,
|
||
such as delayed import side effects and changes to <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code>), there is
|
||
no other observable change in behavior: the imported object is present in the
|
||
module namespace as normal and is transparently loaded whenever first used: its
|
||
status as a “lazy imported object” is not directly observable from Python or
|
||
from C extension code.</p>
|
||
<p>The requirement that the imported object be present in the module namespace as
|
||
usual, even before the import has actually occurred, means that we need some
|
||
kind of “lazy object” placeholder to represent the not-yet-imported object.
|
||
The transparency requirement dictates that this placeholder must never be
|
||
visible to Python code; any reference to it must trigger the import and replace
|
||
it with the real imported object.</p>
|
||
<p>Given the possibility that Python (or C extension) code may pull objects
|
||
directly out of a module <code class="docutils literal notranslate"><span class="pre">__dict__</span></code>, the only way to reliably prevent
|
||
accidental leakage of lazy objects is to have the dictionary itself be
|
||
responsible to ensure resolution of lazy objects on lookup.</p>
|
||
<p>When a lookup finds that the key references a lazy object, it resolves the lazy
|
||
object immediately before returning it. To avoid side effects mutating
|
||
dictionaries midway through iteration, all lazy objects in a dictionary are
|
||
resolved prior to starting an iteration; this could incur a performance penalty
|
||
when using bulk iterations (<code class="docutils literal notranslate"><span class="pre">iter(dict)</span></code>, <code class="docutils literal notranslate"><span class="pre">reversed(dict)</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">dict.__reversed__()</span></code>, <code class="docutils literal notranslate"><span class="pre">dict.keys()</span></code>, <code class="docutils literal notranslate"><span class="pre">iter(dict.keys())</span></code> and
|
||
<code class="docutils literal notranslate"><span class="pre">reversed(dict.keys())</span></code>). To avoid this performance penalty on the vast
|
||
majority of dictionaries, which never contain any lazy objects, we steal a bit
|
||
from the <code class="docutils literal notranslate"><span class="pre">dk_kind</span></code> field for a new <code class="docutils literal notranslate"><span class="pre">dk_lazy_imports</span></code> flag to keep track of
|
||
whether a dictionary may contain lazy objects or not.</p>
|
||
<p>This implementation comprehensively prevents leakage of lazy objects, ensuring
|
||
they are always resolved to the real imported object before anyone can get hold
|
||
of them for any use, while avoiding any significant performance impact on
|
||
dictionaries in general.</p>
|
||
</section>
|
||
<section id="specification">
|
||
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
|
||
<p>Lazy imports are opt-in, and they can be globally enabled either via a new
|
||
<code class="docutils literal notranslate"><span class="pre">-L</span></code> flag to the Python interpreter, or via a call to a new
|
||
<code class="docutils literal notranslate"><span class="pre">importlib.set_lazy_imports()</span></code> function. This function takes two arguments, a
|
||
boolean <code class="docutils literal notranslate"><span class="pre">enabled</span></code> and an <code class="docutils literal notranslate"><span class="pre">excluding</span></code> container. If <code class="docutils literal notranslate"><span class="pre">enabled</span></code> is true, lazy
|
||
imports will be turned on from that point forward. If it is false, they will be
|
||
turned off from that point forward. (Use of the <code class="docutils literal notranslate"><span class="pre">excluding</span></code> keyword is
|
||
discussed below under “Per-module opt-out.”)</p>
|
||
<p>When the flag <code class="docutils literal notranslate"><span class="pre">-L</span></code> is passed to the Python interpreter, a new
|
||
<code class="docutils literal notranslate"><span class="pre">sys.flags.lazy_imports</span></code> is set to <code class="docutils literal notranslate"><span class="pre">True</span></code>, otherwise it exists as <code class="docutils literal notranslate"><span class="pre">False</span></code>.
|
||
This flag is used to propagate <code class="docutils literal notranslate"><span class="pre">-L</span></code> to new Python subprocesses.</p>
|
||
<p>The flag in <code class="docutils literal notranslate"><span class="pre">sys.flags.lazy_imports</span></code> does not necessarily reflect the current
|
||
status of lazy imports, only whether the interpreter was started with the <code class="docutils literal notranslate"><span class="pre">-L</span></code>
|
||
option. Actual current status of whether lazy imports are enabled or not at any
|
||
moment can be retrieved using <code class="docutils literal notranslate"><span class="pre">importlib.is_lazy_imports_enabled()</span></code>, which
|
||
will return <code class="docutils literal notranslate"><span class="pre">True</span></code> if lazy imports are enabled at the call point or <code class="docutils literal notranslate"><span class="pre">False</span></code>
|
||
otherwise.</p>
|
||
<p>When lazy imports are enabled, the loading and execution of all (and only)
|
||
top-level imports is deferred until the imported name is first used. This could
|
||
happen immediately (e.g. on the very next line after the import statement) or
|
||
much later (e.g. while using the name inside a function being called by some
|
||
other code at some later time.)</p>
|
||
<p>For these top level imports, there are two contexts which will make them eager
|
||
(not lazy): imports inside <code class="docutils literal notranslate"><span class="pre">try</span></code> / <code class="docutils literal notranslate"><span class="pre">except</span></code> / <code class="docutils literal notranslate"><span class="pre">finally</span></code> or <code class="docutils literal notranslate"><span class="pre">with</span></code>
|
||
blocks, and star imports (<code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">foo</span> <span class="pre">import</span> <span class="pre">*</span></code>.) Imports inside
|
||
exception-handling blocks (this includes <code class="docutils literal notranslate"><span class="pre">with</span></code> blocks, since those can also
|
||
“catch” and handle exceptions) remain eager so that any exceptions arising from
|
||
the import can be handled. Star imports must remain eager since performing the
|
||
import is the only way to know which names should be added to the namespace.</p>
|
||
<p>Imports inside class definitions or inside functions/methods are not “top
|
||
level” and are never lazy.</p>
|
||
<p>Dynamic imports using <code class="docutils literal notranslate"><span class="pre">__import__()</span></code> or <code class="docutils literal notranslate"><span class="pre">importlib.import_module()</span></code> are
|
||
also never lazy.</p>
|
||
<p>Lazy imports state (i.e. whether they have been enabled, and any excluded
|
||
modules; see below) is per-interpreter, but global within the interpreter (i.e.
|
||
all threads will be affected).</p>
|
||
<section id="example">
|
||
<h3><a class="toc-backref" href="#example" role="doc-backlink">Example</a></h3>
|
||
<p>Say we have a module <code class="docutils literal notranslate"><span class="pre">spam.py</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># simulate some work</span>
|
||
<span class="kn">import</span> <span class="nn">time</span>
|
||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"spam loaded"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>And a module <code class="docutils literal notranslate"><span class="pre">eggs.py</span></code> which imports it:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">spam</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"imports done"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If we run <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-L</span> <span class="pre">eggs.py</span></code>, the <code class="docutils literal notranslate"><span class="pre">spam</span></code> module will never be imported
|
||
(because it is never referenced after the import), <code class="docutils literal notranslate"><span class="pre">"spam</span> <span class="pre">loaded"</span></code> will never
|
||
be printed, and there will be no 10 second delay.</p>
|
||
<p>But if <code class="docutils literal notranslate"><span class="pre">eggs.py</span></code> simply references the name <code class="docutils literal notranslate"><span class="pre">spam</span></code> after importing it, that
|
||
will be enough to trigger the import of <code class="docutils literal notranslate"><span class="pre">spam.py</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">spam</span>
|
||
<span class="nb">print</span><span class="p">(</span><span class="s2">"imports done"</span><span class="p">)</span>
|
||
<span class="n">spam</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Now if we run <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-L</span> <span class="pre">eggs.py</span></code>, we will see the output <code class="docutils literal notranslate"><span class="pre">"imports</span> <span class="pre">done"</span></code>
|
||
printed first, then a 10 second delay, and then <code class="docutils literal notranslate"><span class="pre">"spam</span> <span class="pre">loaded"</span></code> printed after
|
||
that.</p>
|
||
<p>Of course, in real use cases (especially with lazy imports), it’s not
|
||
recommended to rely on import side effects like this to trigger real work. This
|
||
example is just to clarify the behavior of lazy imports.</p>
|
||
<p>Another way to explain the effect of lazy imports is that it is as if each lazy
|
||
import statement had instead been written inline in the source code immediately
|
||
before each use of the imported name. So one can think of lazy imports as
|
||
similar to transforming this code:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">foo</span>
|
||
|
||
<span class="k">def</span> <span class="nf">func1</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="n">foo</span><span class="o">.</span><span class="n">bar</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">func2</span><span class="p">():</span>
|
||
<span class="k">return</span> <span class="n">foo</span><span class="o">.</span><span class="n">baz</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>To this:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">func1</span><span class="p">():</span>
|
||
<span class="kn">import</span> <span class="nn">foo</span>
|
||
<span class="k">return</span> <span class="n">foo</span><span class="o">.</span><span class="n">bar</span><span class="p">()</span>
|
||
|
||
<span class="k">def</span> <span class="nf">func2</span><span class="p">():</span>
|
||
<span class="kn">import</span> <span class="nn">foo</span>
|
||
<span class="k">return</span> <span class="n">foo</span><span class="o">.</span><span class="n">baz</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This gives a good sense of when the import of <code class="docutils literal notranslate"><span class="pre">foo</span></code> will occur under lazy
|
||
imports, but lazy import is not really equivalent to this code transformation.
|
||
There are several notable differences:</p>
|
||
<ul class="simple">
|
||
<li>Unlike in the latter code, under lazy imports the name <code class="docutils literal notranslate"><span class="pre">foo</span></code> still does
|
||
exist in the module’s global namespace, and can be imported or referenced by
|
||
other modules that import this one. (Such references would also trigger the
|
||
import.)</li>
|
||
<li>The runtime overhead of lazy imports is much lower than the latter code; after
|
||
the first reference to the name <code class="docutils literal notranslate"><span class="pre">foo</span></code> which triggers the import, subsequent
|
||
references will have zero import system overhead; they are indistinguishable
|
||
from a normal name reference.</li>
|
||
</ul>
|
||
<p>In a sense, lazy imports turn the import statement into just a declaration of an
|
||
imported name or names, to later be fully resolved when referenced.</p>
|
||
<p>An import in the style <code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">foo</span> <span class="pre">import</span> <span class="pre">bar</span></code> can also be made lazy. When the
|
||
import occurs, the name <code class="docutils literal notranslate"><span class="pre">bar</span></code> will be added to the module namespace as a lazy
|
||
import. The first reference to <code class="docutils literal notranslate"><span class="pre">bar</span></code> will import <code class="docutils literal notranslate"><span class="pre">foo</span></code> and resolve <code class="docutils literal notranslate"><span class="pre">bar</span></code>
|
||
to <code class="docutils literal notranslate"><span class="pre">foo.bar</span></code>.</p>
|
||
</section>
|
||
<section id="intended-usage">
|
||
<h3><a class="toc-backref" href="#intended-usage" role="doc-backlink">Intended usage</a></h3>
|
||
<p>Since lazy imports are a potentially-breaking semantic change, they should be
|
||
enabled only by the author or maintainer of a Python application, who is
|
||
prepared to thoroughly test the application under the new semantics, ensure it
|
||
behaves as expected, and opt-out any specific imports as needed (see below).
|
||
Lazy imports should not be enabled speculatively by the end user of a Python
|
||
application with any expectation of success.</p>
|
||
<p>It is the responsibility of the application developer enabling lazy imports for
|
||
their application to opt-out any library imports that turn out to need to be
|
||
eager for their application to work correctly; it is not the responsibility of
|
||
library authors to ensure that their library behaves exactly the same under lazy
|
||
imports.</p>
|
||
<p>The documentation of the feature, the <code class="docutils literal notranslate"><span class="pre">-L</span></code> flag, and the new <code class="docutils literal notranslate"><span class="pre">importlib</span></code>
|
||
APIs will be clear about the intended usage and the risks of adoption without
|
||
testing.</p>
|
||
</section>
|
||
<section id="implementation">
|
||
<h3><a class="toc-backref" href="#implementation" role="doc-backlink">Implementation</a></h3>
|
||
<p>Lazy imports are represented internally by a “lazy import” object. When a lazy
|
||
import occurs (say <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">foo</span></code> or <code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">foo</span> <span class="pre">import</span> <span class="pre">bar</span></code>), the key <code class="docutils literal notranslate"><span class="pre">"foo"</span></code>
|
||
or <code class="docutils literal notranslate"><span class="pre">"bar"</span></code> is immediately added to the module namespace dictionary, but with
|
||
its value set to an internal-only “lazy import” object that preserves all the
|
||
necessary metadata to execute the import later.</p>
|
||
<p>A new boolean flag in <code class="docutils literal notranslate"><span class="pre">PyDictKeysObject</span></code> (<code class="docutils literal notranslate"><span class="pre">dk_lazy_imports</span></code>) is set to
|
||
signal that this particular dictionary may contain lazy import objects. This
|
||
flag is only used to efficiently resolve all lazy objects in “bulk” operations,
|
||
when a dictionary may contain lazy objects.</p>
|
||
<p>Anytime a key is looked up in a dictionary to extract its value, the
|
||
value is checked to see if it is a lazy import object. If so, the lazy object
|
||
is immediately resolved, the relevant imported modules executed, the lazy
|
||
import object is replaced in the dictionary (if possible) by the actual
|
||
imported value, and the resolved value is returned from the lookup function. A
|
||
dictionary could mutate as part of an import side effect while resolving a lazy
|
||
import object. In this case it is not possible to efficiently replace the key
|
||
value with the resolved object. In this case, the lazy import object will gain
|
||
a cached pointer to the resolved object. On next access that cached reference
|
||
will be returned and the lazy import object will be replaced in the dict with
|
||
the resolved value.</p>
|
||
<p>Because this is all handled internally by the dictionary implementation, lazy
|
||
import objects can never escape from the module namespace to become visible to
|
||
Python code; they are always resolved at their first reference. No stub, dummy
|
||
or thunk objects are ever visible to Python code or placed in <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code>.
|
||
If a module is imported lazily, no entry for it will appear in <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code>
|
||
at all until it is actually imported on first reference.</p>
|
||
<p>If two different modules (<code class="docutils literal notranslate"><span class="pre">moda</span></code> and <code class="docutils literal notranslate"><span class="pre">modb</span></code>) both contain a lazy <code class="docutils literal notranslate"><span class="pre">import</span>
|
||
<span class="pre">foo</span></code>, each module’s namespace dictionary will have an independent lazy import
|
||
object under the key <code class="docutils literal notranslate"><span class="pre">"foo"</span></code>, delaying import of the same <code class="docutils literal notranslate"><span class="pre">foo</span></code> module. This
|
||
is not a problem. When there is first a reference to, say, <code class="docutils literal notranslate"><span class="pre">moda.foo</span></code>, the
|
||
module <code class="docutils literal notranslate"><span class="pre">foo</span></code> will be imported and placed in <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code> as usual, and the
|
||
lazy object under the key <code class="docutils literal notranslate"><span class="pre">moda.__dict__["foo"]</span></code> will be replaced by the
|
||
actual module <code class="docutils literal notranslate"><span class="pre">foo</span></code>. At this point <code class="docutils literal notranslate"><span class="pre">modb.__dict__["foo"]</span></code> will remain a lazy
|
||
import object. When <code class="docutils literal notranslate"><span class="pre">modb.foo</span></code> is later referenced, it will also try to
|
||
<code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">foo</span></code>. This import will find the module already present in
|
||
<code class="docutils literal notranslate"><span class="pre">sys.modules</span></code>, as is normal for subsequent imports of the same module in
|
||
Python, and at this point will replace the lazy import object at
|
||
<code class="docutils literal notranslate"><span class="pre">modb.__dict__["foo"]</span></code> with the actual module <code class="docutils literal notranslate"><span class="pre">foo</span></code>.</p>
|
||
<p>There are two cases in which a lazy import object can escape a dictionary:</p>
|
||
<ul class="simple">
|
||
<li>Into another dictionary: to preserve the performance of bulk-copy operations
|
||
like <code class="docutils literal notranslate"><span class="pre">dict.update()</span></code> and <code class="docutils literal notranslate"><span class="pre">dict.copy()</span></code>, they do not check for or resolve
|
||
lazy import objects. However, if the source dict has the <code class="docutils literal notranslate"><span class="pre">dk_lazy_imports</span></code>
|
||
flag set that indicates it may contain lazy objects, that flag will be
|
||
passed on to the updated/copied dictionary. This still ensures that the lazy
|
||
import object can’t escape into Python code without being resolved.</li>
|
||
<li>Through the garbage collector: lazy imported objects are still Python objects
|
||
and live within the garbage collector; as such, they can be collected and seen
|
||
via e.g. <code class="docutils literal notranslate"><span class="pre">gc.get_objects()</span></code>. If a lazy object becomes
|
||
visible to Python code in this way, it is opaque and inert; it has no useful
|
||
methods or attributes. A <code class="docutils literal notranslate"><span class="pre">repr()</span></code> of it would be shown as something like:
|
||
<code class="docutils literal notranslate"><span class="pre"><lazy_object</span> <span class="pre">'fully.qualified.name'></span></code>.</li>
|
||
</ul>
|
||
<p>When a lazy object is added to a dictionary, the flag <code class="docutils literal notranslate"><span class="pre">dk_lazy_imports</span></code> is set.
|
||
Once set, the flag is only cleared if <em>all</em> lazy import objects in the
|
||
dictionary are resolved, e.g. prior to dictionary iteration.</p>
|
||
<p>All dictionary iteration methods involving values (such as <code class="docutils literal notranslate"><span class="pre">dict.items()</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">dict.values()</span></code>, <code class="docutils literal notranslate"><span class="pre">PyDict_Next()</span></code> etc.) will attempt to resolve <em>all</em> lazy
|
||
import objects in the dictionary prior to starting the iteration. Since only
|
||
(some) module namespace dictionaries will ever have <code class="docutils literal notranslate"><span class="pre">dk_lazy_imports</span></code> set, the
|
||
extra overhead of resolving all lazy import objects inside a dictionary is only
|
||
paid by those dictionaries that need it. Minimizing the overhead on normal
|
||
non-lazy dictionaries is the sole purpose of the <code class="docutils literal notranslate"><span class="pre">dk_lazy_imports</span></code> flag.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">PyDict_Next</span></code> will attempt to resolve all lazy import objects the first time
|
||
position <code class="docutils literal notranslate"><span class="pre">0</span></code> is accessed, and those imports could fail with exceptions. Since
|
||
<code class="docutils literal notranslate"><span class="pre">PyDict_Next</span></code> cannot set an exception, <code class="docutils literal notranslate"><span class="pre">PyDict_Next</span></code> will return <code class="docutils literal notranslate"><span class="pre">0</span></code>
|
||
immediately in this case, and any exception will be printed to stderr as an
|
||
unraisable exception.</p>
|
||
<p>For this reason, this PEP introduces <code class="docutils literal notranslate"><span class="pre">PyDict_NextWithError</span></code>, which works in
|
||
the same way as <code class="docutils literal notranslate"><span class="pre">PyDict_Next</span></code>, but which can set an error when returning <code class="docutils literal notranslate"><span class="pre">0</span></code>
|
||
and this should be checked via <code class="docutils literal notranslate"><span class="pre">PyErr_Occurred()</span></code> after the call.</p>
|
||
<p>The eagerness of imports within <code class="docutils literal notranslate"><span class="pre">try</span></code> / <code class="docutils literal notranslate"><span class="pre">except</span></code> / <code class="docutils literal notranslate"><span class="pre">with</span></code> blocks or within
|
||
class or function bodies is handled in the compiler via a new
|
||
<code class="docutils literal notranslate"><span class="pre">EAGER_IMPORT_NAME</span></code> opcode that always imports eagerly. Top-level imports use
|
||
<code class="docutils literal notranslate"><span class="pre">IMPORT_NAME</span></code>, which may be lazy or eager depending on <code class="docutils literal notranslate"><span class="pre">-L</span></code> and/or
|
||
<code class="docutils literal notranslate"><span class="pre">importlib.set_lazy_imports()</span></code>.</p>
|
||
</section>
|
||
<section id="debugging">
|
||
<h3><a class="toc-backref" href="#debugging" role="doc-backlink">Debugging</a></h3>
|
||
<p>Debug logging from <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-v</span></code> will include logging whenever an import
|
||
statement has been encountered but execution of the import will be deferred.</p>
|
||
<p>Python’s <code class="docutils literal notranslate"><span class="pre">-X</span> <span class="pre">importtime</span></code> feature for profiling import costs adapts naturally
|
||
to lazy imports; the profiled time is the time spent actually importing.</p>
|
||
<p>Although lazy import objects are not generally visible to Python code, in some
|
||
debugging cases it may be useful to check from Python code whether the value at
|
||
a given key in a given dictionary is a lazy import object, without triggering
|
||
its resolution. For this purpose, <code class="docutils literal notranslate"><span class="pre">importlib.is_lazy_import()</span></code> can be used:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">importlib</span> <span class="kn">import</span> <span class="n">is_lazy_import</span>
|
||
|
||
<span class="kn">import</span> <span class="nn">foo</span>
|
||
|
||
<span class="n">is_lazy_import</span><span class="p">(</span><span class="nb">globals</span><span class="p">(),</span> <span class="s2">"foo"</span><span class="p">)</span>
|
||
|
||
<span class="n">foo</span>
|
||
|
||
<span class="n">is_lazy_import</span><span class="p">(</span><span class="nb">globals</span><span class="p">(),</span> <span class="s2">"foo"</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>In this example, if lazy imports have been enabled the first call to
|
||
<code class="docutils literal notranslate"><span class="pre">is_lazy_import</span></code> will return <code class="docutils literal notranslate"><span class="pre">True</span></code> and the second will return <code class="docutils literal notranslate"><span class="pre">False</span></code>.</p>
|
||
</section>
|
||
<section id="per-module-opt-out">
|
||
<h3><a class="toc-backref" href="#per-module-opt-out" role="doc-backlink">Per-module opt-out</a></h3>
|
||
<p>Due to the backwards compatibility issues mentioned below, it may be necessary
|
||
for an application using lazy imports to force some imports to be eager.</p>
|
||
<p>In first-party code, since imports inside a <code class="docutils literal notranslate"><span class="pre">try</span></code> or <code class="docutils literal notranslate"><span class="pre">with</span></code> block are never
|
||
lazy, this can be easily accomplished:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">try</span><span class="p">:</span> <span class="c1"># force these imports to be eager</span>
|
||
<span class="kn">import</span> <span class="nn">foo</span>
|
||
<span class="kn">import</span> <span class="nn">bar</span>
|
||
<span class="k">finally</span><span class="p">:</span>
|
||
<span class="k">pass</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>This PEP proposes to add a new <code class="docutils literal notranslate"><span class="pre">importlib.eager_imports()</span></code> context manager,
|
||
so the above technique can be less verbose and doesn’t require comments to
|
||
clarify its intent:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">importlib</span> <span class="kn">import</span> <span class="n">eager_imports</span>
|
||
|
||
<span class="k">with</span> <span class="n">eager_imports</span><span class="p">():</span>
|
||
<span class="kn">import</span> <span class="nn">foo</span>
|
||
<span class="kn">import</span> <span class="nn">bar</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Since imports within context managers are always eager, the <code class="docutils literal notranslate"><span class="pre">eager_imports()</span></code>
|
||
context manager can just be an alias to a null context manager. The context
|
||
manager’s effect is not transitive: <code class="docutils literal notranslate"><span class="pre">foo</span></code> and <code class="docutils literal notranslate"><span class="pre">bar</span></code> will be imported
|
||
eagerly, but imports within those modules will still follow the usual laziness
|
||
rules.</p>
|
||
<p>The more difficult case can occur if an import in third-party code that can’t
|
||
easily be modified must be forced to be eager. For this purpose,
|
||
<code class="docutils literal notranslate"><span class="pre">importlib.set_lazy_imports()</span></code> takes a second optional keyword-only
|
||
<code class="docutils literal notranslate"><span class="pre">excluding</span></code> argument, which can be set to a container of module names within
|
||
which all imports will be eager:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">importlib</span> <span class="kn">import</span> <span class="n">set_lazy_imports</span>
|
||
|
||
<span class="n">set_lazy_imports</span><span class="p">(</span><span class="n">excluding</span><span class="o">=</span><span class="p">[</span><span class="s2">"one.mod"</span><span class="p">,</span> <span class="s2">"another"</span><span class="p">])</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The effect of this is also shallow: all imports within <code class="docutils literal notranslate"><span class="pre">one.mod</span></code> will be
|
||
eager, but not imports in all modules imported by <code class="docutils literal notranslate"><span class="pre">one.mod</span></code>.</p>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">excluding</span></code> parameter of <code class="docutils literal notranslate"><span class="pre">set_lazy_imports()</span></code> can be a container of any
|
||
kind that will be checked to see whether it contains a module name. If the
|
||
module name is contained in the object, imports within it will be eager. Thus,
|
||
arbitrary opt-out logic can be encoded in a <code class="docutils literal notranslate"><span class="pre">__contains__</span></code> method:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">re</span>
|
||
<span class="kn">from</span> <span class="nn">importlib</span> <span class="kn">import</span> <span class="n">set_lazy_imports</span>
|
||
|
||
<span class="k">class</span> <span class="nc">Checker</span><span class="p">:</span>
|
||
<span class="k">def</span> <span class="fm">__contains__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="n">re</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="s2">"foo\.[^.]+\.logger"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
|
||
|
||
<span class="n">set_lazy_imports</span><span class="p">(</span><span class="n">excluding</span><span class="o">=</span><span class="n">Checker</span><span class="p">())</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>If Python was executed with the <code class="docutils literal notranslate"><span class="pre">-L</span></code> flag, then lazy imports will already be
|
||
globally enabled, and the only effect of calling <code class="docutils literal notranslate"><span class="pre">set_lazy_imports(True,</span>
|
||
<span class="pre">excluding=...)</span></code> will be to globally set the eager module names/callback. If
|
||
<code class="docutils literal notranslate"><span class="pre">set_lazy_imports(True)</span></code> is called with no <code class="docutils literal notranslate"><span class="pre">excluding</span></code> argument, the
|
||
exclusion list/callback will be cleared and all eligible imports (module-level
|
||
imports not in <code class="docutils literal notranslate"><span class="pre">try/except/with</span></code>, and not <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">*</span></code>) will be lazy from that
|
||
point forward.</p>
|
||
<p>This opt-out system is designed to maintain the possibility of local reasoning
|
||
about the laziness of an import. You only need to see the code of one module,
|
||
and the <code class="docutils literal notranslate"><span class="pre">excluding</span></code> argument to <code class="docutils literal notranslate"><span class="pre">set_lazy_imports</span></code>, if any, to know whether
|
||
a given import will be eager or lazy.</p>
|
||
</section>
|
||
<section id="testing">
|
||
<h3><a class="toc-backref" href="#testing" role="doc-backlink">Testing</a></h3>
|
||
<p>The CPython test suite will pass with lazy imports enabled (with some tests
|
||
skipped). One buildbot should run the test suite with lazy imports enabled.</p>
|
||
</section>
|
||
<section id="c-api">
|
||
<h3><a class="toc-backref" href="#c-api" role="doc-backlink">C API</a></h3>
|
||
<p>For authors of C extension modules, the proposed public C API is as follows:</p>
|
||
<table class="docutils align-default">
|
||
<colgroup>
|
||
<col style="width: 50.0%" />
|
||
<col style="width: 50.0%" />
|
||
</colgroup>
|
||
<thead>
|
||
<tr class="row-odd"><th class="head">C API</th>
|
||
<th class="head">Python API</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">PyObject</span> <span class="pre">*PyImport_SetLazyImports(PyObject</span> <span class="pre">*enabled,</span> <span class="pre">PyObject</span> <span class="pre">*excluding)</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">importlib.set_lazy_imports(enabled:</span> <span class="pre">bool</span> <span class="pre">=</span> <span class="pre">True,</span> <span class="pre">*,</span> <span class="pre">excluding:</span> <span class="pre">typing.Container[str]</span> <span class="pre">|</span> <span class="pre">None</span> <span class="pre">=</span> <span class="pre">None)</span></code></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">PyDict_IsLazyImport(PyObject</span> <span class="pre">*dict,</span> <span class="pre">PyObject</span> <span class="pre">*name)</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">importlib.is_lazy_import(dict:</span> <span class="pre">typing.Dict[str,</span> <span class="pre">object],</span> <span class="pre">name:</span> <span class="pre">str)</span> <span class="pre">-></span> <span class="pre">bool</span></code></td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">PyImport_IsLazyImportsEnabled()</span></code></td>
|
||
<td><code class="docutils literal notranslate"><span class="pre">importlib.is_lazy_imports_enabled()</span> <span class="pre">-></span> <span class="pre">bool</span></code></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><code class="docutils literal notranslate"><span class="pre">void</span> <span class="pre">PyDict_ResolveLazyImports(PyObject</span> <span class="pre">*dict)</span></code></td>
|
||
<td></td>
|
||
</tr>
|
||
<tr class="row-even"><td><code class="docutils literal notranslate"><span class="pre">PyDict_NextWithError()</span></code></td>
|
||
<td></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<ul class="simple">
|
||
<li><code class="docutils literal notranslate"><span class="pre">void</span> <span class="pre">PyDict_ResolveLazyImports(PyObject</span> <span class="pre">*dict)</span></code> resolves all lazy objects
|
||
in a dictionary, if any. To be used prior calling <code class="docutils literal notranslate"><span class="pre">PyDict_NextWithError()</span></code>
|
||
or <code class="docutils literal notranslate"><span class="pre">PyDict_Next()</span></code>.</li>
|
||
<li><code class="docutils literal notranslate"><span class="pre">PyDict_NextWithError()</span></code>, works the same way as <code class="docutils literal notranslate"><span class="pre">PyDict_Next()</span></code>, with
|
||
the exception it propagates any errors to the caller by returning <code class="docutils literal notranslate"><span class="pre">0</span></code> and
|
||
setting an exception. The caller should use <code class="docutils literal notranslate"><span class="pre">PyErr_Occurred()</span></code> to check for any
|
||
errors.</li>
|
||
</ul>
|
||
</section>
|
||
</section>
|
||
<section id="backwards-compatibility">
|
||
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
|
||
<p>This proposal preserves full backwards compatibility when the feature is
|
||
disabled, which is the default.</p>
|
||
<p>Even when enabled, most code will continue to work normally without any
|
||
observable change (other than improved startup time and memory usage.)
|
||
Namespace packages are not affected: they work just as they do currently,
|
||
except lazily.</p>
|
||
<p>In some existing code, lazy imports could produce currently unexpected results
|
||
and behaviors. The problems that we may see when enabling lazy imports in an
|
||
existing codebase are related to:</p>
|
||
<section id="import-side-effects">
|
||
<h3><a class="toc-backref" href="#import-side-effects" role="doc-backlink">Import Side Effects</a></h3>
|
||
<p>Import side effects that would otherwise be produced by the execution of
|
||
imported modules during the execution of import statements will be deferred
|
||
until the imported objects are used.</p>
|
||
<p>These import side effects may include:</p>
|
||
<ul class="simple">
|
||
<li>code executing any side-effecting logic during import;</li>
|
||
<li>relying on imported submodules being set as attributes in the parent module.</li>
|
||
</ul>
|
||
<p>A relevant and typical affected case is the <a class="reference external" href="https://click.palletsprojects.com/">click</a> library for building Python command-line
|
||
interfaces. If e.g. <code class="docutils literal notranslate"><span class="pre">cli</span> <span class="pre">=</span> <span class="pre">click.group()</span></code> is defined in <code class="docutils literal notranslate"><span class="pre">main.py</span></code>, and
|
||
<code class="docutils literal notranslate"><span class="pre">sub.py</span></code> imports <code class="docutils literal notranslate"><span class="pre">cli</span></code> from <code class="docutils literal notranslate"><span class="pre">main</span></code> and adds subcommands to it via
|
||
decorator (<code class="docutils literal notranslate"><span class="pre">@cli.command(...)</span></code>), but the actual <code class="docutils literal notranslate"><span class="pre">cli()</span></code> call is in
|
||
<code class="docutils literal notranslate"><span class="pre">main.py</span></code>, then lazy imports may prevent the subcommands from being
|
||
registered, since in this case Click is depending on side effects of the import
|
||
of <code class="docutils literal notranslate"><span class="pre">sub.py</span></code>. In this case the fix is to ensure the import of <code class="docutils literal notranslate"><span class="pre">sub.py</span></code> is
|
||
eager, e.g. by using the <code class="docutils literal notranslate"><span class="pre">importlib.eager_imports()</span></code> context manager.</p>
|
||
</section>
|
||
<section id="dynamic-paths">
|
||
<h3><a class="toc-backref" href="#dynamic-paths" role="doc-backlink">Dynamic Paths</a></h3>
|
||
<p>There could be issues related to dynamic Python import paths; particularly,
|
||
adding (and then removing after the import) paths from <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>:</p>
|
||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s2">"/path/to/foo/module"</span><span class="p">)</span>
|
||
<span class="kn">import</span> <span class="nn">foo</span>
|
||
<span class="k">del</span> <span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
|
||
<span class="n">foo</span><span class="o">.</span><span class="n">Bar</span><span class="p">()</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>In this case, with lazy imports enabled, the import of <code class="docutils literal notranslate"><span class="pre">foo</span></code> will not actually
|
||
occur while the addition to <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> is present.</p>
|
||
<p>An easy fix for this (which also improves the code style and ensures cleanup)
|
||
would be to place the <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> modifications in a context manager. This
|
||
resolves the issue, since imports inside a <code class="docutils literal notranslate"><span class="pre">with</span></code> block are always eager.</p>
|
||
</section>
|
||
<section id="deferred-exceptions">
|
||
<h3><a class="toc-backref" href="#deferred-exceptions" role="doc-backlink">Deferred Exceptions</a></h3>
|
||
<p>Exceptions that occur during a lazy import bubble up and erase the
|
||
partially-constructed module(s) from <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code>, just as exceptions during
|
||
normal import do.</p>
|
||
<p>Since errors raised during a lazy import will occur later than they would if
|
||
the import were eager (i.e. wherever the name is first referenced), it is also
|
||
possible that they could be accidentally caught by exception handlers that did
|
||
not expect the import to be running within their <code class="docutils literal notranslate"><span class="pre">try</span></code> block, leading to
|
||
confusion.</p>
|
||
</section>
|
||
</section>
|
||
<section id="drawbacks">
|
||
<h2><a class="toc-backref" href="#drawbacks" role="doc-backlink">Drawbacks</a></h2>
|
||
<p>Downsides of this PEP include:</p>
|
||
<ul class="simple">
|
||
<li>It provides a subtly incompatible semantics for the behavior of Python
|
||
imports. This is a potential burden on library authors who may be asked by their
|
||
users to support both semantics, and is one more possibility for Python
|
||
users/readers to be aware of.</li>
|
||
<li>Some popular Python coding patterns (notably centralized registries populated
|
||
by a decorator) rely on import side effects and may require explicit opt-out to
|
||
work as expected with lazy imports.</li>
|
||
<li>Exceptions can be raised at any point while accessing names representing lazy
|
||
imports, this could lead to confusion and debugging of unexpected exceptions.</li>
|
||
</ul>
|
||
<p>Lazy import semantics are already possible and even supported today in the
|
||
Python standard library, so these drawbacks are not newly introduced by this
|
||
PEP. So far, existing usage of lazy imports by some applications has not proven
|
||
a problem. But this PEP could make the usage of lazy imports more popular,
|
||
potentially exacerbating these drawbacks.</p>
|
||
<p>These drawbacks must be weighed against the significant benefits offered by this
|
||
PEP’s implementation of lazy imports. Ultimately these costs will be higher if
|
||
the feature is widely used; but wide usage also indicates the feature provides a
|
||
lot of value, perhaps justifying the costs.</p>
|
||
</section>
|
||
<section id="security-implications">
|
||
<h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2>
|
||
<p>Deferred execution of code could produce security concerns if process owner,
|
||
shell path, <code class="docutils literal notranslate"><span class="pre">sys.path</span></code>, or other sensitive environment or contextual states
|
||
change between the time the <code class="docutils literal notranslate"><span class="pre">import</span></code> statement is executed and the time the
|
||
imported object is first referenced.</p>
|
||
</section>
|
||
<section id="performance-impact">
|
||
<h2><a class="toc-backref" href="#performance-impact" role="doc-backlink">Performance Impact</a></h2>
|
||
<p>The reference implementation has shown that the feature has negligible
|
||
performance impact on existing real-world codebases (Instagram Server, several
|
||
CLI programs at Meta, Jupyter notebooks used by Meta researchers), while
|
||
providing substantial improvements to startup time and memory usage.</p>
|
||
<p>The reference implementation shows <a class="reference external" href="https://gist.github.com/ericsnowcurrently/d027ff4130dedec3b58ab1f55be11e8c">no measurable change</a>
|
||
in aggregate performance on the <a class="reference external" href="https://github.com/python/pyperformance">pyperformance benchmark suite</a>.</p>
|
||
</section>
|
||
<section id="how-to-teach-this">
|
||
<h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2>
|
||
<p>Since the feature is opt-in, beginners should not encounter it by default.
|
||
Documentation of the <code class="docutils literal notranslate"><span class="pre">-L</span></code> flag and <code class="docutils literal notranslate"><span class="pre">importlib.set_lazy_imports()</span></code> can
|
||
clarify the behavior of lazy imports.</p>
|
||
<p>The documentation should also clarify that opting into lazy imports is opting
|
||
into a non-standard semantics for Python imports, which could cause Python
|
||
libraries to break in unexpected ways. The responsibility to identify these
|
||
breakages and work around them with an opt-out (or stop using lazy imports)
|
||
rests entirely with the person choosing to enable lazy imports for their
|
||
application, not with the library author. Python libraries are under no
|
||
obligation to support lazy import semantics. Politely reporting an
|
||
incompatibility may be useful to the library author, but they may choose to
|
||
simply say their library does not support use with lazy imports, and this is a
|
||
valid choice.</p>
|
||
<p>Some best practices to deal with some of the issues that could arise and to
|
||
better take advantage of lazy imports are:</p>
|
||
<ul class="simple">
|
||
<li>Avoid relying on import side effects. Perhaps the most common reliance on
|
||
import side effects is the registry pattern, where population of some external
|
||
registry happens implicitly during the importing of modules, often via
|
||
decorators. Instead, the registry should be built via an explicit call that does
|
||
a discovery process to find decorated functions or classes in explicitly
|
||
nominated modules.</li>
|
||
<li>Always import needed submodules explicitly, don’t rely on some other import
|
||
to ensure a module has its submodules as attributes. That is, unless there is an
|
||
explicit <code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">.</span> <span class="pre">import</span> <span class="pre">bar</span></code> in <code class="docutils literal notranslate"><span class="pre">foo/__init__.py</span></code>, always do <code class="docutils literal notranslate"><span class="pre">import</span>
|
||
<span class="pre">foo.bar;</span> <span class="pre">foo.bar.Baz</span></code>, not <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">foo;</span> <span class="pre">foo.bar.Baz</span></code>. The latter only works
|
||
(unreliably) because the attribute <code class="docutils literal notranslate"><span class="pre">foo.bar</span></code> is added as a side effect of
|
||
<code class="docutils literal notranslate"><span class="pre">foo.bar</span></code> being imported somewhere else. With lazy imports this may not always
|
||
happen in time.</li>
|
||
<li>Avoid using star imports, as those are always eager.</li>
|
||
</ul>
|
||
</section>
|
||
<section id="reference-implementation">
|
||
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
|
||
<p>The initial implementation is available as part of <a class="reference external" href="https://github.com/facebookincubator/cinder">Cinder</a>. This reference implementation
|
||
is in use within Meta and has proven to achieve improvements in startup time
|
||
(and total runtime for some applications) in the range of 40%-70%, as well as
|
||
significant reduction in memory footprint (up to 40%), thanks to not needing to
|
||
execute imports that end up being unused in the common flow.</p>
|
||
<p>An <a class="reference external" href="https://github.com/Kronuz/cpython/pull/17">updated reference implementation based on CPython main branch</a> is also available.</p>
|
||
</section>
|
||
<section id="rejected-ideas">
|
||
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
|
||
<section id="wrapping-deferred-exceptions">
|
||
<h3><a class="toc-backref" href="#wrapping-deferred-exceptions" role="doc-backlink">Wrapping deferred exceptions</a></h3>
|
||
<p>To reduce the potential for confusion, exceptions raised in the
|
||
course of executing a lazy import could be replaced by a <code class="docutils literal notranslate"><span class="pre">LazyImportError</span></code>
|
||
exception (a subclass of <code class="docutils literal notranslate"><span class="pre">ImportError</span></code>), with a <code class="docutils literal notranslate"><span class="pre">__cause__</span></code> set to the
|
||
original exception.</p>
|
||
<p>Ensuring that all lazy import errors are raised as <code class="docutils literal notranslate"><span class="pre">LazyImportError</span></code> would
|
||
reduce the likelihood that they would be accidentally caught and mistaken for a
|
||
different expected exception. However, in practice we have seen cases, e.g.
|
||
inside tests, where failing modules raise <code class="docutils literal notranslate"><span class="pre">unittest.SkipTest</span></code> exception and
|
||
this too would end up being wrapped in <code class="docutils literal notranslate"><span class="pre">LazyImportError</span></code>, making such tests
|
||
fail because the true exception type is hidden. The drawbacks here seem to
|
||
outweigh the hypothetical case where unexpected deferred exceptions are caught
|
||
by mistake.</p>
|
||
</section>
|
||
<section id="per-module-opt-in">
|
||
<h3><a class="toc-backref" href="#per-module-opt-in" role="doc-backlink">Per-module opt-in</a></h3>
|
||
<p>A per-module opt-in using future imports (i.e.
|
||
<code class="docutils literal notranslate"><span class="pre">from</span> <span class="pre">__future__</span> <span class="pre">import</span> <span class="pre">lazy_imports</span></code>) does not make sense because
|
||
<code class="docutils literal notranslate"><span class="pre">__future__</span></code> imports are not feature flags, they are for transition to
|
||
behaviors which will become default in the future. It is not clear if lazy
|
||
imports will ever make sense as the default behavior, so we should not
|
||
promise this with a <code class="docutils literal notranslate"><span class="pre">__future__</span></code> import.</p>
|
||
<p>There are other cases where a library might desire to locally opt-in to lazy
|
||
imports for a particular module; e.g. a lazy top-level <code class="docutils literal notranslate"><span class="pre">__init__.py</span></code> for a
|
||
large library, to make its subcomponents accessible as lazy attributes. For now,
|
||
to keep the feature simpler, this PEP chooses to focus on the “application” use
|
||
case and does not address the library use case. The underlying laziness
|
||
mechanism introduced in this PEP could be used in the future to address this use
|
||
case as well.</p>
|
||
</section>
|
||
<section id="explicit-syntax-for-individual-lazy-imports">
|
||
<h3><a class="toc-backref" href="#explicit-syntax-for-individual-lazy-imports" role="doc-backlink">Explicit syntax for individual lazy imports</a></h3>
|
||
<p>If the primary objective of lazy imports were solely to work around import
|
||
cycles and forward references, an explicitly-marked syntax for particular
|
||
targeted imports to be lazy would make a lot of sense. But in practice it would
|
||
be very hard to get robust startup time or memory use benefits from this
|
||
approach, since it would require converting most imports within your code base
|
||
(and in third-party dependencies) to use the lazy import syntax.</p>
|
||
<p>It would be possible to aim for a “shallow” laziness where only the top-level
|
||
imports of subsystems from the main module are made explicitly lazy, but then
|
||
imports within the subsystems are all eager. This is extremely fragile, though
|
||
– it only takes one mis-placed import to undo the carefully constructed
|
||
shallow laziness. Globally enabling lazy imports, on the other hand, provides
|
||
in-depth robust laziness where you always pay only for the imports you use.</p>
|
||
<p>There may be use cases (e.g. for static typing) where individually-marked lazy
|
||
imports are desirable to avoid forward references, but the perf/memory benefits
|
||
of globally lazy imports are not needed. Since this is a different set of
|
||
motivating use cases and requires new syntax, we prefer not to include it in
|
||
this PEP. Another PEP could build on top of this implementation and propose the
|
||
additional syntax.</p>
|
||
</section>
|
||
<section id="environment-variable-to-enable-lazy-imports">
|
||
<h3><a class="toc-backref" href="#environment-variable-to-enable-lazy-imports" role="doc-backlink">Environment variable to enable lazy imports</a></h3>
|
||
<p>Providing an environment variable opt-in lends itself too easily to abuse of the
|
||
feature. It may seem tempting for a Python user to, for instance, globally set
|
||
the environment variable in their shell in the hopes of speeding up all the
|
||
Python programs they run. This usage with untested programs is likely to lead to
|
||
spurious bug reports and maintenance burden for the authors of those tools. To
|
||
avoid this, we choose not to provide an environment variable opt-in at all.</p>
|
||
</section>
|
||
<section id="removing-the-l-flag">
|
||
<h3><a class="toc-backref" href="#removing-the-l-flag" role="doc-backlink">Removing the <code class="docutils literal notranslate"><span class="pre">-L</span></code> flag</a></h3>
|
||
<p>We do provide the <code class="docutils literal notranslate"><span class="pre">-L</span></code> CLI flag, which could in theory be abused in a similar
|
||
way by an end user running an individual Python program that is run with
|
||
<code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">somescript.py</span></code> or <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-m</span> <span class="pre">somescript</span></code> (rather than distributed
|
||
via Python packaging tools). But the potential scope for misuse is much less
|
||
with <code class="docutils literal notranslate"><span class="pre">-L</span></code> than an environment variable, and <code class="docutils literal notranslate"><span class="pre">-L</span></code> is valuable for some
|
||
applications to maximize startup time benefits by ensuring that all imports from
|
||
the start of a process will be lazy, so we choose to keep it.</p>
|
||
<p>It is already the case that running arbitrary Python programs with command line
|
||
flags they weren’t intended to be used with (e.g. <code class="docutils literal notranslate"><span class="pre">-s</span></code>, <code class="docutils literal notranslate"><span class="pre">-S</span></code>, <code class="docutils literal notranslate"><span class="pre">-E</span></code>, or
|
||
<code class="docutils literal notranslate"><span class="pre">-I</span></code>) can have unexpected and breaking results. <code class="docutils literal notranslate"><span class="pre">-L</span></code> is nothing new in this
|
||
regard.</p>
|
||
</section>
|
||
<section id="half-lazy-imports">
|
||
<h3><a class="toc-backref" href="#half-lazy-imports" role="doc-backlink">Half-lazy imports</a></h3>
|
||
<p>It would be possible to eagerly run the import loader to the point of finding
|
||
the module source, but then defer the actual execution of the module and
|
||
creation of the module object. The advantage of this would be that certain
|
||
classes of import errors (e.g. a simple typo in the module name) would be
|
||
caught eagerly instead of being deferred to the use of an imported name.</p>
|
||
<p>The disadvantage would be that the startup time benefits of lazy imports would
|
||
be significantly reduced, since unused imports would still require a filesystem
|
||
<code class="docutils literal notranslate"><span class="pre">stat()</span></code> call, at least. It would also introduce a possibly non-obvious split
|
||
between <em>which</em> import errors are raised eagerly and which are delayed, when
|
||
lazy imports are enabled.</p>
|
||
<p>This idea is rejected for now on the basis that in practice, confusion about
|
||
import typos has not been an observed problem with the reference
|
||
implementation. Generally delayed imports are not delayed forever, and errors
|
||
show up soon enough to be caught and fixed (unless the import is truly unused.)</p>
|
||
<p>Another possible motivation for half-lazy imports would be to allow modules
|
||
themselves to control via some flag whether they are imported lazily or eagerly.
|
||
This is rejected both on the basis that it requires half-lazy imports, giving up
|
||
some of the performance benefits of import laziness, and because in general
|
||
modules do not decide how or when they are imported, the module importing them
|
||
decides that. There isn’t clear rationale for this PEP to invert that control;
|
||
instead it just provides more options for the importing code to make the
|
||
decision.</p>
|
||
</section>
|
||
<section id="lazy-dynamic-imports">
|
||
<h3><a class="toc-backref" href="#lazy-dynamic-imports" role="doc-backlink">Lazy dynamic imports</a></h3>
|
||
<p>It would be possible to add a <code class="docutils literal notranslate"><span class="pre">lazy=True</span></code> or similar option to
|
||
<code class="docutils literal notranslate"><span class="pre">__import__()</span></code> and/or <code class="docutils literal notranslate"><span class="pre">importlib.import_module()</span></code>, to enable them to
|
||
perform lazy imports. That idea is rejected in this PEP for lack of a clear
|
||
use case. Dynamic imports are already far outside the <a class="pep reference internal" href="../pep-0008/" title="PEP 8 – Style Guide for Python Code">PEP 8</a> code style
|
||
recommendations for imports, and can easily be made precisely as lazy as
|
||
desired by placing them at the desired point in the code flow. These aren’t
|
||
commonly used at module top level, which is where lazy imports applies.</p>
|
||
</section>
|
||
<section id="deep-eager-imports-override">
|
||
<h3><a class="toc-backref" href="#deep-eager-imports-override" role="doc-backlink">Deep eager-imports override</a></h3>
|
||
<p>The proposed <code class="docutils literal notranslate"><span class="pre">importlib.eager_imports()</span></code> context manager and excluded modules
|
||
in the <code class="docutils literal notranslate"><span class="pre">importlib.set_lazy_imports(excluding=...)</span></code> override all have shallow
|
||
effects: they only force eagerness for the location they are applied to, not
|
||
transitively. It would be possible to provide a deep/transitive version of one
|
||
or both. That idea is rejected in this PEP because the implementation would be
|
||
complex (taking into account threads and async code), experience with the
|
||
reference implementation has not shown it to be necessary, and because it
|
||
prevents local reasoning about laziness of imports.</p>
|
||
<p>A deep override can lead to confusing behavior because the
|
||
transitively-imported modules may be imported from multiple locations, some of
|
||
which use the “deep eager override” and some of which don’t. Thus those modules
|
||
may still be imported lazily initially, if they are first imported from a
|
||
location that doesn’t have the override.</p>
|
||
<p>With deep overrides it is not possible to locally reason about whether a given
|
||
import will be lazy or eager. With the behavior specified in this PEP, such
|
||
local reasoning is possible.</p>
|
||
</section>
|
||
<section id="making-lazy-imports-the-default-behavior">
|
||
<h3><a class="toc-backref" href="#making-lazy-imports-the-default-behavior" role="doc-backlink">Making lazy imports the default behavior</a></h3>
|
||
<p>Making lazy imports the default/sole behavior of Python imports, instead of
|
||
opt-in, would have some long-term benefits, in that library authors would
|
||
(eventually) no longer need to consider the possibility of both semantics.</p>
|
||
<p>However, the backwards-incompatibilies are such that this could only be
|
||
considered over a long time frame, with a <code class="docutils literal notranslate"><span class="pre">__future__</span></code> import. It is not at
|
||
all clear that lazy imports should become the default import semantics for
|
||
Python.</p>
|
||
<p>This PEP takes the position that the Python community needs more experience with
|
||
lazy imports before considering making it the default behavior, so that is
|
||
entirely left to a possible future PEP.</p>
|
||
</section>
|
||
</section>
|
||
<section id="copyright">
|
||
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
|
||
<p>This document is placed in the public domain or under the
|
||
CC0-1.0-Universal license, whichever is more permissive.</p>
|
||
</section>
|
||
</section>
|
||
<hr class="docutils" />
|
||
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0690.rst">https://github.com/python/peps/blob/main/peps/pep-0690.rst</a></p>
|
||
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0690.rst">2023-09-09 17:39:29 GMT</a></p>
|
||
|
||
</article>
|
||
<nav id="pep-sidebar">
|
||
<h2>Contents</h2>
|
||
<ul>
|
||
<li><a class="reference internal" href="#abstract">Abstract</a></li>
|
||
<li><a class="reference internal" href="#motivation">Motivation</a></li>
|
||
<li><a class="reference internal" href="#rationale">Rationale</a></li>
|
||
<li><a class="reference internal" href="#specification">Specification</a><ul>
|
||
<li><a class="reference internal" href="#example">Example</a></li>
|
||
<li><a class="reference internal" href="#intended-usage">Intended usage</a></li>
|
||
<li><a class="reference internal" href="#implementation">Implementation</a></li>
|
||
<li><a class="reference internal" href="#debugging">Debugging</a></li>
|
||
<li><a class="reference internal" href="#per-module-opt-out">Per-module opt-out</a></li>
|
||
<li><a class="reference internal" href="#testing">Testing</a></li>
|
||
<li><a class="reference internal" href="#c-api">C API</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a><ul>
|
||
<li><a class="reference internal" href="#import-side-effects">Import Side Effects</a></li>
|
||
<li><a class="reference internal" href="#dynamic-paths">Dynamic Paths</a></li>
|
||
<li><a class="reference internal" href="#deferred-exceptions">Deferred Exceptions</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#drawbacks">Drawbacks</a></li>
|
||
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
|
||
<li><a class="reference internal" href="#performance-impact">Performance Impact</a></li>
|
||
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
|
||
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
|
||
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
|
||
<li><a class="reference internal" href="#wrapping-deferred-exceptions">Wrapping deferred exceptions</a></li>
|
||
<li><a class="reference internal" href="#per-module-opt-in">Per-module opt-in</a></li>
|
||
<li><a class="reference internal" href="#explicit-syntax-for-individual-lazy-imports">Explicit syntax for individual lazy imports</a></li>
|
||
<li><a class="reference internal" href="#environment-variable-to-enable-lazy-imports">Environment variable to enable lazy imports</a></li>
|
||
<li><a class="reference internal" href="#removing-the-l-flag">Removing the <code class="docutils literal notranslate"><span class="pre">-L</span></code> flag</a></li>
|
||
<li><a class="reference internal" href="#half-lazy-imports">Half-lazy imports</a></li>
|
||
<li><a class="reference internal" href="#lazy-dynamic-imports">Lazy dynamic imports</a></li>
|
||
<li><a class="reference internal" href="#deep-eager-imports-override">Deep eager-imports override</a></li>
|
||
<li><a class="reference internal" href="#making-lazy-imports-the-default-behavior">Making lazy imports the default behavior</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#copyright">Copyright</a></li>
|
||
</ul>
|
||
|
||
<br>
|
||
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0690.rst">Page Source (GitHub)</a>
|
||
</nav>
|
||
</section>
|
||
<script src="../_static/colour_scheme.js"></script>
|
||
<script src="../_static/wrap_tables.js"></script>
|
||
<script src="../_static/sticky_banner.js"></script>
|
||
</body>
|
||
</html> |