Due to lack of time, I was only able to assess PEP810 rather superficially.
Conclusion at a time was:
- It has no critical issues
- It does deliver what it promises with good performance
- It has sharp edges regarding convenience and general matter of quite drastic paradigm shift
Starting from Alternative path for explicit lazy imports - #41.
By now, I have alpha version of an alternative, which is Pure Python and purely external.
I have hit most of milestones by now and hopefully this can shed a bit more light on the matter of things:
- I have a working thing
- It does do everything that PEP810. except syntax and
from ... import ... - PEP810 is only 1 of 2 paradigms (both of them work nicely together)
- It is compatible with 3.8 - 3.14
- I have tested it on scientific apps, various plugins and scripts
So although it is still most most likely has undiscovered issues, but it is largely functional.
1. Functionality
API:
import_module(name, lazy=-1|0|1|2|3|4, mby=False)lazy_imports_on(deferred=False)
Aliases for import_module are:
import_eager = partial(import_module, lazy=-1)
import_maybe = partial(import_module, lazy=-1, mby=1)
import_lazy = partial(import_module, lazy=1)
import_background = partial(import_module, lazy=2)
import_deferred = partial(import_module, lazy=3)
import_background_deferred = partial(import_module, lazy=4)
Notes:
- (1) and (2) work in sync, sensibly.
- Both paradigms are implemented:
LazyLoader-like (does checks at definition)- PEP810-like (leaves raw proxy in place without checks)
2. Implementation
<1000 lines of code
But ~60% is replication of importlib._bootstrap.
And it could use some refactoring.
I would say it would be roughly 300 lines of code to implement to CPython
3. Couple of examples
lazy_imports_on()
import tkinter.dialog as tkd
print(type(sys.modules['tkinter']))
print(type(tkd))
<class 'lazymodule'>
<class 'lazymodule'>
lazy_imports_on(deferred=True)
import tkinter.dialog as tkd
print(type(sys.modules['tkinter']))
print(type(tkd))
<class 'deferredmodule'>
<class 'deferredmodule'>
PEP810-like approach (deferredmodule) still leaves proxies in sys.modules, but they are as raw as possible and not doing any work in advance or raising any errors.
4. Performance
Performance on local machine is nearly identical (only 7% slower than PEP810):
pass code LL D pep810
----------------------------------------
3.13 : 190 560 420 410
3.14 : 25 260 140 140 130
LL - LazyLoader-like
D - deferred (PEP810-like)
5. Considerations
So, by now, I like LazyLoader much more.
- It feels much more native and Pythonic.
- Minimal code change needed to adapt
- It is much more extensible
- It integrates to existing machinery rather smoothly
- Does leave lazy objects in
sys.modules, thus any subsequent standard imports will just retrieve it. But I think it is a good thing - it provides extra performance opportunities. And also, still feels as a part of imports.
PEP810 approach is a paradigm shift and feels rather alien.
None of the applications “just worked” - it is quite big extra dimension to keep in mind.
The main advantage of it is that it does not do expensive file system operations that are costly for remote imports.
Thus, I find its approach complementary.
For now.
In the long term I will aim to solve it with specialised caching and only keep LazyLoader-like approach.
6. My conclusions
By now, it is clear to me what my intuition was telling me initially:
- There is a lot more to explore in this direction. To me it feels like this direction in general is expansive - the more I dig, the more opportunities I find. E.g. The checks that are done at definition can be defined by Loader and calibrated per type.
- And PEP810 is premature commitment.
It:
- Reserves the syntax
- Locks-in new paradigm, which is rather alien - none of the code just works after switching the global setting on
- Has limited functionality
- Provides little opportunities to extend. Or at least not easily.
Alternatively, the organic growth would look like:
- Incrementally develop functional variant without committing to
from ... import ... - Slowly explore variations and different needs
- Once it has matured enough (with involvement of community), nail down the syntax
- Finally add
from ... import .... Either:
A. If more genral “deferred evaluation” is ready - use it
B. Implement more generic mechanism of PEP810 reification.LazyObject(func, *args, **kwds)which gets reified that can be used for anything.
Note, before (5) sys.modules would be inevitably populated, but after (5) anything can be done.
PEP810 has been accepted already.
I don’t have hopes for things to change.
But I have raised my concerns.
And I wanted to follow through.
The time provided did not allow for anything proper in time.
So this is delayed.