Recent lazy import PEP810 has been submitted.
This thread is to showcase an alternative path and compare the 2.
1. How to teach this (and the way it actually works)
The workings are simple:
- Any executed imports (lazy or not) live in
sys.modules - Any import (lazy or not) first checks
sys.modulesbefore doing any work - Thus, the user / developer can always observe and adapt to the current state of affairs
2. Showcase
NOTE, import statements can be easily made to reify previous lazy imports. It is only an option to propagate them, can be done either / both ways - doing things this way allows a lot of flexibility and customisation of behaviour. It is just the one that turned out to be in initial concept.
from importlib.util import *
turtle = lazy_import('turtle')
print(type(turtle)) # lazymodule
# Any further non-eager imports simply pick up `lazyimport`
# from `sys.modules`
import turtle as turtle2
print(type(turtle2)) # lazymodule
# `from module import attribute`
# reifies any pending lazymodules
from turtle import up
print(type(turtle)) # module
print(type(turtle2)) # module
This provides a mechanism to set lazy imports at the main application,
and all subsequent dependencies will simply pick them up from sys.modules:
# main.py
from importlib.util import *
tkinter = lazy_import('tkinter')
import submodule # @ submodule: lazymodule
submodule.foo() # I am not using tkinter
# tkinter is still lazy
print(type(tkinter)) # lazymodule
submodule.bar() # I have used tkinter
# Not anymore
print(type(tkinter)) # module
# submodule.py
import tkinter
def foo():
print('I am not using tkinter')
def bar():
tkinter.ACTIVE
print('I have used tkinter')
print('@ submodule', type(tkinter))
And if a package wants to enforce eager import, then it can be done explicitly:
from importlib.util import *
tkinter = lazy_import('tkinter')
print(type(tkinter)) # lazymodule
numpy = eager_import('tkinter')
print(type(tkinter)) # module
Parent packages (if not already imported) are recursively made lazy as well:
from importlib.util import *
tkinter_ttk = lazy_import('tkinter.ttk')
print(type(tkinter_ttk)) # lazymodule
import tkinter
print(type(tkinter)) # lazymodule
Error handling:
In sync with non-lazy imports:
import sys
from importlib.util import *
try:
lazy_import('no_turtle')
except ModuleNotFoundError:
print("'no_turtle' package not found, install by $ pip install no_turtle")
sys.exit(1)
Filtering:
More advanced filtering can be implemented,
but for now it is simply exclusion of the highest level package
from importlib.util import *
exclude_lazy('tkinter')
tkinter_ttk = lazy_import('tkinter.ttk')
print(type(tkinter_ttk)) # module
import tkinter
print(type(tkinter)) # module
Syntax:
For now, it is simple functions, but it can be wrapped up in dedicated syntax.
Which is a benefit as initial implementation is independent of syntax, making it simpler and more modular design.
Bonus:
import time
from importlib.util import *
tkinter = background_import('tkinter')
print(type(tkinter)) # lazyimport
time.sleep(1)
print(type(tkinter)) # import
3. Implementation
101 lines of pure Python code: gh-140722: Improve lazy functionality of `importlib.util` by dg-pb · Pull Request #140723 · python/cpython · GitHub
4. Pros and Cons versus the path of PEP810
Cons:
- Does not support
from module import attribute. However, PEP810 does not supportfrom module import *, so this is just a slight functionality downgrade, which is already incomplete. As opposed to something which provides “complete syntactic substitution”. Also, there are no obstacles extending this to support it and it might be a very natural extension once more general “deferred evaluation” happens, which ideally given time one day will provide the most optimal path for more general problem. - Slower - might not be suitable to just import everything lazy without consideration.
- Open to extend this list.
Pros:
- Simpler
- More explicit
- More interactive
- Provides more control
- Manages errors at definition
- Does not venture into black magic
- Solves
async importproblem off the shelf - Makes use of all the good work done in
importlib - Does not require dedicated syntax for functionality to work
- Is orthogonal to any future endeavours in “deferred evaluation”