Live data from Hacker News

PEP 810 – Explicit lazy imports

peps.python.org

61–70 of 247 posts

Re: PEP 810 – Explicit lazy imports

#61
post #26

I don't like the idea of introducing a new keyword. We need a backward compatible solution. I feel like Python needs some kind of universal annotation syntax such as in go (comments) or in Rust (macros). New keyword means all parsers, lsps, editors should be updated. I’m pretty sure there will be new keywords in Python in the future that only solve one thing.

Not sure if this can be made backward compatible. Right now all the imports are getting resolved at runtime example in a code like below from file1 import function1 When you write this, the entire file1 module is executed right away, which may trigger side effects. If lazy imports suddenly defer execution, those side effects won’t run until much later (or not at all, if the code path isn’t hit). That shift in timing…

I was talking about syntax. I'm pretty sure there will be new features that will require a new keyword or syntax given the speed of Python growth. It can be universal, for example same as decorator, but it can be applied anywhere.

   from lazy import make_lazy
   from package import module @make_lazy @local @nogil

Let's say this syntax gets introduced in Python 3.16. The @nogil feature can be introduced in 3.17. If such code is running in Python 3.16, the @nogil marker will be ignored.

The problem with new keywords is that you have to stick to the newest Python version every time a new keyword is added. Older Python versions will give a syntax error. It's a big problem for libraries. You need to wait for 3-5 years before adding it to a library. There are a lot of people who still use Python 3.8 from 2019.

Re: PEP 810 – Explicit lazy imports

#62
post #32

Love this. My https://llm.datasette.io/ CLI tool supports plugins, and people were complaining about really slow start times even for commands like "llm --help" - it turned out there were popular plugins that did things like import pytorch at the base level, so the entire startup was blocked on heavy imports. I ended up adding a note to the plugin author docs suggesting lazy loading inside of functions - https://llm.…

You can implement this from your tool today: https://news.ycombinator.com/item?id=45467489

Note that this is global to the entire process, so for example if you make an import of Numpy lazy this way, then so are the imports of all the sub-modules. Meaning that large parts of Numpy might not be imported at all if they aren't needed, but pauses for importing individual modules might be distributed unpredictably across the runtime.

Edit: from further experimentation, it appears that if the source does something like `import foo.bar.baz` then `foo` and `foo.bar` will still be eagerly loaded, and only `foo.bar.baz` itself is deferred. This might be part of what the PEP meant by "mostly". But it might also be possible to improve my implementation to fix that.

Re: PEP 810 – Explicit lazy imports

#63
post #61

Earlier quoted context omitted.

Not sure if this can be made backward compatible. Right now all the imports are getting resolved at runtime example in a code like below from file1 import function1 When you write this, the entire file1 module is executed right away, which may trigger side effects. If lazy imports suddenly defer execution, those side effects won’t run until much later (or not at all, if the code path isn’t hit). That shift in timing…

I was talking about syntax. I'm pretty sure there will be new features that will require a new keyword or syntax given the speed of Python growth. It can be universal, for example same as decorator, but it can be applied anywhere. from lazy import make_lazy from package import module @make_lazy @local @nogil Let's say this syntax gets introduced in Python 3.16. The @nogil feature can be introduced in 3.17. If such co…

Understood make sense

Re: PEP 810 – Explicit lazy imports

#64
post #45

Lazy imports have been proposed before, and were rejected most recently back in 2022: https://discuss.python.org/t/pep-690-lazy-imports-again/1966... . If I recall correctly, lazy imports are a feature supported in Cinder, Meta's version of CPython, and the PEP was driven by folks that worked on Cinder. Last time, a lot of the discussion centered around questions like: Should this be opt-in or opt-out? At what level?…

Oof. I wish they could support version imports import torch==2.6.0+cu124 import numpy>=1.2.6 and support having multiple simultaneous versions of any Python library installed. End this conda/virtualenv/docker/bazel/[pick your poison] mess

How would version imports be handled across the codebase? Also, what do you gain with those over PEP 723 – Inline script metadata? https://packaging.python.org/en/latest/specifications/inline...

Re: PEP 810 – Explicit lazy imports

#65
post #47

This is needed, but I don't like new keywords. What I would love, for many reasons, is if we could decorate statements. Then things like: 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…

For what it's worth, you can already do: x = retry(3)(failure_prone_call)(y)

And, for that matter, expensive_module = lazy(importlib.import_module)('expensive_module') .

Re: PEP 810 – Explicit lazy imports

#66
post #60
post #45

Earlier quoted context omitted.

Oof. I wish they could support version imports import torch==2.6.0+cu124 import numpy>=1.2.6 and support having multiple simultaneous versions of any Python library installed. End this conda/virtualenv/docker/bazel/[pick your poison] mess

It's been explained many times before why this is not possible: the library doesn't actually have a version number. The distribution of source code on PyPI has a version number, but the name of this is not connected to the name of any module or package you import in the source code. The distribution can validly define zero or more modules (packages are a subset of modules, represented using the same type in the Pytho…

You could absolutely have this be part of the language in any regard. The question then becomes how does one implement it in a reasonable way. I think every package should have a __version__ property you should be able to call, then you could have versioned imports.

In fact there's already many packages already defining __version__ at a package level.

https://packaging.python.org/en/latest/discussions/versionin...

Edit: What they are solving with UV is at the moment of standing up an environment, but you're more concerned about code-level protection, where are they're more concerned about environment setup protection for versioning.

Re: PEP 810 – Explicit lazy imports

#67
I don't hate it but I don't love it. It sounds like everyone will start writing `lazy` before essentially every single import, with rare exceptions where eager importing is actually needed. That makes Python code visually noisier. And with no plan to ever change the default, the noise will stay forever.

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.

Re: PEP 810 – Explicit lazy imports

#68
post #33

We tend to prefer explicit top-level imports specifically because they reveal dependency problems as soon as the program starts, rather than potentially hours or days later when a specific code path is executed.

As a counterpoint, having all the imports automatically deferred would instantly dramatically speed up pip for short tasks.

  $ time pip install --disable-pip-version-check
  ERROR: You must give at least one requirement to install (see "pip help install")

  real    0m0.399s
  user    0m0.360s
  sys     0m0.041s
Almost all of this time is spent importing (and later unloading) ultimately useless vendored code. From my testing (hacking the wrapper script to output some diagnostics), literally about 500 modules get imported in total (on top of the baseline for a default Python process), including almost a hundred modules related to Requests and its dependencies, even though no web request was necessary for this command.

Re: PEP 810 – Explicit lazy imports

#69
post #38
post #17

Earlier quoted context omitted.

All my code which uses import probing would fail, such as fallbacks: try: import module except ImportError: import slow_module as module Conditional support testing would also break, like having tests which only run if module2 is available: try: import module2 except ImportError: def if_has_module2(f): return unittest.skip("module2 not available")(f) else: def if_has_module2(f): return f @if_has_module2 class TestMod…

You have to opt in with the lazy import keyword no matter what. This pep also prevents lazy import in try catch. I think your concern matters if ‘module’ itself has a lazy import that you want to check exists. With this you now need to be more rigorous in check those sub dependencies. This can already happen with non top level imports so it is not a necessarily a new issue, but could become more prevalent if there is…

My post was a response to the sedatk conjectural "I wonder how much things would break if all imports were lazy by default."

I have zero concerns about this PEP and look forward to its implementation.

Re: PEP 810 – Explicit lazy imports

#70
post #7

Earlier quoted context omitted.

Yeah unfortunately in real world Python code people put side effects in their modules all the time.

In Python? I almost never see that. And I’ve certainly never signed off on a PR that did that.

Recursively importing other modules is itself a side effect.

In fact, all the code you see in the module is "side effects", in a sense. A `class` body, for example, has to actually run at import time, creating the class object and attaching it as an attribute of the module object. Similarly for functions. Even a simple assignment of a constant actually has to run at module import. And all of these things add up.

Further, if there isn't already cached bytecode available for the module, by default it will be written to disk as part of the import process. That's inarguably a side effect.

Post reply on HN