by azhenley on 10/3/25, 6:24 PM with 240 comments
by simonw on 10/3/25, 7:54 PM
I ended up adding a note to the plugin author docs suggesting lazy loading inside of functions - https://llm.datasette.io/en/stable/plugins/advanced-model-pl... - but having a core Python language feature for this would be really nice.
by charliermarsh on 10/3/25, 7:35 PM
I hope this proposal succeeds. I would love to use this feature.
by comex on 10/3/25, 8:53 PM
I would have preferred a system where modules opt in to being lazy-loaded, with no extra syntax on the import side. That would simplify things since only large libraries would have to care about laziness. To be fair, in such a design, the interpreter would have to eagerly look up imports on the filesystem to decide whether they should be lazy-loaded. And there are probably other downsides I'm not thinking of.
by wrmsr on 10/3/25, 7:23 PM
I was skeptical and cautious with it at first but I've since moved large chunks of my codebase to it - it's caused surprisingly few problems (honestly none besides forgetting to handle some import-time registration in some modules) and the speed boost is addictive.
by Spivak on 10/3/25, 7:46 PM
lazily import package.foo
vs
defer import package.foo
Also the grammar is super weird for from imports. lazy from package import foo
vs.
from package defer import foo.by thayne on 10/3/25, 9:56 PM
And that is actually a problem for more than just performance. In some cases, importing at the top might actually just fail. For example if you need a platform specific library, but only if it is running on that platform.
by nilslindemann on 10/3/25, 10:56 PM
They make that, what should be the default, a special case. Soon, every new code will use "lazy". The long term effect of such changes is a verbose language syntax.
They should have had a period where one, if they want lazy imports, has to do "from __future__ import lazy_import". After that period, lazy imports become the default. For old-style immediate imports, introduce a syntax: "import now foo" and "from foo import now bar".
All which authors of old code would have to do is run a provided fix script in the root directory of their code.
by zahlman on 10/3/25, 8:34 PM
The use of these sorts of Python import internals is highly non-obvious. The Stack Overflow Q&A I found about it (https://stackoverflow.com/questions/42703908/) doesn't result in an especially nice-looking UX.
So here's a proof of concept in existing Python for getting all imports to be lazy automatically, with no special syntax for the caller:
import sys
import threading # needed for python 3.13, at least at the REPL, because reasons
from importlib.util import LazyLoader # this has to be eagerly imported!
class LazyPathFinder(sys.meta_path[-1]): # <class '_frozen_importlib_external.PathFinder'>
@classmethod
def find_spec(cls, fullname, path=None, target=None):
base = super().find_spec(fullname, path, target)
base.loader = LazyLoader(base.loader)
return base
sys.meta_path[-1] = LazyPathFinder
We've replaced the "meta path finder" (which implements the logic "when the module isn't in sys.modules, look on sys.path for source code and/or bytecode, including bytecode in __pycache__ subfolders, and create a 'spec' for it") with our own wrapper. The "loader" attached to the resulting spec is replaced with an importlib.util.LazyLoader instance, which wraps the base PathFinder's provided loader. When an import statement actually imports the module, the name will actually get bound to a <class 'importlib.util._LazyModule'> instance, rather than an ordinary module. Attempting to access any attribute of this instance will trigger the normal module loading procedure — which even replaces the global name.Now we can do:
import this # nothing shows up
print(type(this)) # <class 'importlib.util._LazyModule'>
rot13 = this.s # the module is loaded, printing the Zen
print(type(this)) # <class 'module'>
That said, I don't know what the PEP means by "mostly" here.by TheMrZZ on 10/3/25, 7:05 PM
by bcoates on 10/4/25, 12:59 AM
Previously, if you had some thread hazardous code at module import time, it was highly likely to only run during the single threaded process startup phase, so it was likely harmless. Lazy loading is going to unearth these errors in the most inconvenient way (as Heisenbugs)
(Function level import can trigger this as well, but the top of a function is at least a slightly more deterministic place for imports to happen, and an explicit line of syntax triggering the import/bug)
by aftbit on 10/3/25, 7:57 PM
by f311a on 10/3/25, 7:43 PM
I’m pretty sure there will be new keywords in Python in the future that only solve one thing.
by jacquesm on 10/4/25, 12:37 AM
by film42 on 10/3/25, 7:23 PM
However, there is a pattern in python to raise an error if, say, pandas doesn't have an excel library installed, which is fine. In the future, will maintainers opt to include a bunch of unused libraries since they won't negatively impact startup time? (Think pandas including 3-4 excel parsers by default, since it will only be loaded when called). It's a much better UX, but, now if you opt out of lazy loading, your code will take longer to load than without it.
by jmward01 on 10/3/25, 8:19 PM
import expensive_module
could be:
@lazy
import expensive_module
or you could do:
@retry(3)
x = failure_prone_call(y)
lazy is needed, but maybe there is a more basic change that could give more power with more organic syntax, and not create a new keyword that is special purpose (and extending an already special purpose keyword)
by ajb on 10/4/25, 7:22 AM
by pjjpo on 10/4/25, 9:31 AM
- lazy imports are a hugely impactful feature
- lazy imports are already possible without syntax
This means any libraries that get large benefit from lazy imports already use import statements within functions. They can't really use the new feature since 3.14 EoL is _2030_, forever from now. The __lazy_modules__ syntax preserves compatibility only but being eager, not performance - libraries that need lazy imports can't use it until 2030.
This means that the primary target for a long time is CLI authors, which can have a more strict python target and is mentioned many times in the PEP, and libraries that don't have broad Python version support (meaning not just works but works well), which indicates they are probably not major libraries.
Unless the feature gets backported to 2+ versions, it feels not so compelling. But given how it modifies the interpreter to a reasonable degree, I wonder if even any backport is on the table.
by Boxxed on 10/3/25, 8:27 PM
Soooo instead now we're going to be in a situation where you're going to be writing "lazy import ..." 99% of the time: unless you're a barbarian, you basically never have side effects at the top level.
by f33d5173 on 10/4/25, 7:06 PM
On the other hand, it would create confusion for users of a library when the performance hit of importing a library was delayed to the site of usage. They might not expect, for example, a lag to occur there. I don't think it would cause outright breakage, but people might not like the way it behaved.
by the_mitsuhiko on 10/3/25, 7:48 PM
by forrestthewoods on 10/3/25, 7:36 PM
What I want is for imports to not suck and be slow. I’ve had projects where it was faster to compile and run C++ than launch and start a Python CLI. It’s so bad.
by dataangel on 10/3/25, 11:02 PM
by Too on 10/4/25, 6:46 AM
If one could dream, modules should have to explicitly declare whether they have side-effects or not, with a marker at the top of the module. Not declaring this and trying anything except declaring a function or class should lead to type-checker errors. Modules declaring this pure-marker then automatically become lazy. Others should require explicit "import_with_side_effects" keyword.
__pure__ = True
import logging
import threading
app = Flask() # << ImpureError
sys.path.append() # << ImpureError
with open(.. ) # << ImpureError
logging.basicSetup() # << ImpureError
if foo: # << ImpureError (arguable)
@app.route("/foo") # Questionable
def serve(): # OK
...
serve() # << ImpureError
t = threading.Thread(target=serve) # << ImpureError
All of this would be impossible today, given how much the Python relies on metaprogramming. Even standard library exposes functions to create classes on the fly like Enum and dataclasses, that are difficult to assert as either pure or impure. With more and more of the ecosystem embracing typed Python, this metaprogramming is reducing into a less dynamic subset. Type checkers and LSPs must have at least some awareness of these modules, without executing them as plain python-code.by rplnt on 10/3/25, 8:11 PM
by wmichelin on 10/4/25, 9:37 PM
Edit
> A somewhat common way to delay imports is to move the imports into functions (inline imports), but this practice requires more work to implement and maintain, and can be subverted by a single inadvertent top-level import. Additionally, it obfuscates the full set of dependencies for a module. Analysis of the Python standard library shows that approximately 17% of all imports outside tests (nearly 3500 total imports across 730 files) are already placed inside functions or methods specifically to defer their execution. This demonstrates that developers are already manually implementing lazy imports in performance-sensitive code, but doing so requires scattering imports throughout the codebase and makes the full dependency graph harder to understand at a glance.
I think this is a really weak foundation for this language feature. We don't need it.
by croemer on 10/4/25, 9:29 PM
by sedatk on 10/3/25, 7:06 PM
by romanows on 10/3/25, 11:47 PM
by Galanwe on 10/3/25, 7:10 PM
by throwaway81523 on 10/4/25, 12:27 AM
by jeremyscanvic on 10/3/25, 7:44 PM
by thijsvandien on 10/3/25, 10:36 PM
by hoten on 10/3/25, 7:12 PM
by gorgoiler on 10/3/25, 9:33 PM
> The dominant convention in Python code is to place all imports at the beginning of the file. This avoids repetition, makes import dependencies clear and minimizes runtime overhead.
> A somewhat common way to delay imports is to move the imports into functions, but this practice requires more work [and] obfuscates the full set of dependencies for a module.
The first part is just saying the traditions exist because the traditions have always existed. Traditions are allowed to change!
The second part is basically saying if you do your own logic-based lazy imports (inline imports in functions) then you’re going against the traditions. Again, traditions are allowed to change!
The point about the import graph being obfuscated would ring more true if Python didn’t already provide lightning fast static analysis tools like ast. If you care about import graphs at the module level then you’re probably already automating everything with ast anyway, at which point you just walk the whole tree looking for imports rather than the top level.
So, really, the whole argument for a new lazy keyword (instead of inlining giant imports where they are needed) is because people like to see import pytorch at the top of the file, and baulk at seeing it — and will refuse to even look for it! — anywhere else? Hmmm.
What does seem like a pain in the ass is having to do this kind of repetitive crap (which they mention in their intro):
def f1():
import pytorch
…
def f2():
import pytorch
…
def f3():
import pytorch
…
But perhaps the solution is a pattern where you put all your stuff like that in your own module and it’s that module which is lazy loaded instead?by thatxliner on 10/5/25, 12:53 AM
> A world in which Python only supported imports behaving in a lazy manner would likely be great...we do not envision the Python langauge transitioning to a world where lazy imports are the default...this concept would add complexity to our ecosystem.
Why can't lazy be the default and instead propose an `eager` syntax? The only argument I can imagine is that there's some API that runs a side effect based on importing, but perhaps making it eager for modules with side effects would be a sufficient temporary fix?
by the__alchemist on 10/3/25, 7:25 PM
by nodesocket on 10/3/25, 9:42 PM
by d_burfoot on 10/4/25, 12:48 AM
I could, and sometimes do, go through all the imports to figure out which ones are taking a long time to load, but it's a chore.
by mekoka on 10/3/25, 10:46 PM
by shiandow on 10/4/25, 9:35 AM
I mean I get why that makes the most sense in most scenarios, but it is not as if the problem of having to choose between declaring dependencies up front or deferring expensive imports until needed does not happen in functions.
Take for instance a function that quickly fails because the arguments are incorrect, it might do a whole bunch of imports that only make sense for that function but which are made immediately obsolete.
It feels like it is forbidden just because someone thought it wasn't a good coding style but to me there is no obvious reason it couldn't work.
by Hendrikto on 10/4/25, 11:20 AM
by waynesonfire on 10/3/25, 8:20 PM
by hhthrowaway1230 on 10/4/25, 4:04 PM
by behnamoh on 10/3/25, 7:34 PM
by keeganpoppen on 10/3/25, 7:30 PM
by enriquto on 10/3/25, 9:29 PM
def antislash(A, b):
from numpy.linalg import solve
return solve(A, b)
thus numpy.linalg is only imported the first time you call the antislash function. Much cleaner than a global import.Ignore wrong traditions. Put all imports in the innermost scopes of your code!
by Alir3z4 on 10/3/25, 7:40 PM
I know/heard there are "some" (which I haven't seen by the way) libraries that depend on import side effects, but the advantage is much bigger.
First of all, the circular import problem will go away, especially on type hints. Although there was a PEP or recent addition to make the annotation not not cause such issue.
Second and most important of all, is the launch time of Python applications. A CLI that uses many different parts of the app has to wait for all the imports to be done.
The second point becomes a lot painful when you have a large application, like a Django project where the auto reload becomes several seconds. Not only auto reload crawls, the testing cycle is slow as well. Every time you want to run test command, it has to wait several seconds. Painful.
So far the solution has been to do the lazy import by importing inside the methods where it's required. That is something, I never got to like to be honest.
Maybe it will be fixed in Python 4, where the JIT uses the type hints as well /s
by IshKebab on 10/3/25, 7:13 PM