Thanks, Greg, for stealing my thunder I’d been working on this post for a few days and was about to post it, but yes, what you say is pretty much true.
(For context: I recently joined Meta, and I’m part of the team that includes Germán and used to include Carl, the original authors of PEP 690. I’ve talked with Germán and the rest of the team quite a bit about lazy imports.)
Lazy imports really matter for startup time when you have dependencies that you don’t always use. They don’t have to be that large, either: even things like typing
and enum
, with all their metaclass magic, cause noticeable slowdowns, and delaying their imports (or the imports of the things using them) makes interactive tools a lot snappier, and process restarts less expensive. This is especially true when combining a lazily imported typing
with PEP 563 (from __future__ import annotations
) or PEP 649. I know we all hope we can make all of Python fast enough so that modules aren’t expensive to execute anymore, but just not doing things until (and unless) they’re actually necessary is a really simple way of achieving that goal, too.
For what it’s worth, Meta is actively using the original PEP 690 lazy import mechanism, and still sees a lot of value in it (specifically, startup time, which matters for a wide variety of things). We also see some of the downsides, as we enable lazy imports for more internal binaries. We’re mitigating some of that with internal tooling to try and determine if it seems safe to lazily import a module. While that obviously reduces the speedups (it’s fairly conservative, which is kinda the point) even the safe approach sees significant improvements. We intend to open-source that tooling when that’s useful (if lazy imports become a real thing in Python, implicitly or explicitly.)
We’re also working on an alternate proposal for explicit lazy imports. The internal mechanism would remain the same (special dict entries that resolve imports when the lazy objects are accessed, so no special syntax to “resolve” a lazy import), but the way to get an import to be lazy would be explicit. The laziness still applies to everything (module finding, loading and execution), because that seems like a much more straight-forward approach than trying to split those up – especially since execution usually leads to more imports, and having the delayed module already found and loaded under potentially different search paths/importers feels like a very confusing situation.
With the explicit lazy import syntax implementation, it would be incredibly easy to also have an implicit lazy import mode, possibly with an opt-out or opt-in list of module names. We’re not sure yet whether that should be part of the PEP or not, but it seems very likely that if explicit lazy imports were added, we’d still carry the (much smaller) patch for the implicit version internally. We could have implicit lazy imports as a tool to test whether a module can be loaded lazily (e.g. by running its tests with lazy imports enabled).
(I should also point out that it looks like the lazy import mechanism, the part that evaluates imports when the lazy objects are accessed, could be used to implement the “deferred expression” syntax proposed in Backquotes for deferred expression, at least in the global namespace. I think that’s probably a bad idea, but it’s possible.)
We unfortunately have some other stuff to finish first, but we hope to have a concrete proposal soon, probably come January.