After switching most of my attention to this several days ago, I have covered the essentials in time.
And I agree, this will inevitably bring substantial benefit to most users.
While strongly supportive of lazy imports, I recommend two key changes to ensure smooth adoption: (1) add eager import syntax, and (2) enable minimal error checking by default.
I am still uncomfortable with the pace of this.
It has been said in relation to this: “The biggest change to Python in a decade is coming.”
I would add “with the least proportional community exposure time”.
E.g. PEP572 seems to have had ~4-5 months of solid exposure.
Concerns regarding overlap with more general “deferred eval” eliminated.
In line with Alternative path for explicit lazy imports - #32 by bwoodsend.
In the remote case (remote, because inability to pass deferrals into functions would unlikely cut it) that this type of deferred eval is wanted in the future, this doesn’t obstruct it.
Not storing placeholders / proxies in sys.modules makes this orthogonal with LazyLoader.
As accurately pointed out by Alternative path for explicit lazy imports - #38 by ncoghlan.
I think this could use some polish to be better suited for adoption by average use case:
1. Addition of eager import
“we don’t want to encourage use of the global flags”
I think this is the biggest value it brings - making large portion (if not all) lazy.
If only few sparse lazy imports are needed, then LazyLoader (or one of other existent solutions) is sufficient.
The very reason why this is worthwhile is the ability to make most of imports lazy.
And the concept itself seems to be taken from places where this was the primary concern.
Thus, scenario:
10 independent modules that all import “single side-effect module”.
(Largely in line with flat structure of Python’s standard library.)
No natural place to add filters.
importlib.import_module does the trick, but then it needs to be imported in all 10 modules.
And given that this had a big weight of it being syntax-based, eager import does seem like a very convenient option. I think this would cover common cases for most of users without needing to add any filters and allow minimum effort switch to global lazy imports, which delivers maximum benefits.
This was the first thing that I wanted to do when trying to adapt to this.
And the fact that I needed to either use filters (think about where to put them (if there is a good place at all) and how to use them) or import importlib everywhere and resort to functional imports alongside the syntactic ones did not feel smooth.
2. ModuleNotFound error
There were 3 reasons indicated for not doing minimal error checks:
- The issue is particularly acute on NFS-backed filesystems and distributed storage
While I agree that “no error checks at all” is a good option to have, I don’t think it should be default.
This does not represent average use case.
I propose to have a flag to “opt out from minimal checks”.
- More critically, separating finding from loading creates the worst of both worlds for error handling.
It is not about error checks, but about keeping the default as much in-line with standard imports as possible for as smooth switch as possible for average use case.
I tried adapting it to my libraries and the biggest change that is needed to adapt is to replace all top level module logic on detecting what is installed and what is not.
So, given that:
a) I know that I have not made any syntactic mistakes
b) I know that all is configured properly
The pain point of adoption is:
try:
import compression.gzip as gzip
except ModuleNotFoundError:
import gzip
Alternative would be to provide a builtin or utility placed conveniently somewhere else:
if module_locatable('compression.gzip'):
...
However, I don’t think such sacrifice is very optimal given that the most common case is locally stored imports.
- Additionally, there are technical limitations: finding the module does not guarantee the import will succeed, nor even that it will not raise ImportError.
That is true, but that does not mean that there shouldn’t be an attempt to align the behaviour to the maximum possible degree for the sake of familiar transition.
It is an import statement, if no checks are done, then it is an import wrapped in independent delay mechanism and its functioning has nothing to do with import, but is only a generic wrapper for any operation, while __import__ just happens to be the one at hand.
I don’t think “majority opt-in” for this case represents optimal benefits for the community.
If this is tailored for the use case of “remotely stored imports”, then there needs to be evidence of the frequency and significance of this. Otherwise, I think it is safe to assume that the average use case is “local imports”.
Apart from the above, this addition, IMO, is close to the best that can be done (which is as it should be, given that it has already been tested in other places and the number of experts behind this):
from ... import ... is useful. Although not implementing this would reduce major portion of complexity, but I think the complexity is more than justified given manifestos of its desirability and the fact that complexity is not that big in relation to complexity that already exists in places of implementation.
- The benefits are non-trivial. I am sure for the cases of dependency nightmares this is invaluable (although I urge not to treat it as common case and consider my suggestions above (especially (2))), but even for my simple current needs this brings observable benefits. Apart from the obvious, I have use cases where I have interactive utilities that re-evaluate scripts for interactivity. This cuts the delay in half making them resemble performance of commercial software as opposed to quickly scribbled conveniences that they are:
ns pass +core.py
------------------------
3.13 : 190 +150
3.14 : 190 +130
lazy : 20 +120
lazy=on : 20 + 70
where core.py is common dependencies of 90% of my stuff.