Skip to content

Commit 874085d

Browse files
dschultstefanv
andauthored
Warn and discourage lazy.load of subpackages (#57)
* Add warning and change docs to discourage subpackages with load() * Update test to use builtin lib stable over python versions * Minor edits * Also update README --------- Co-authored-by: Stefan van der Walt <sjvdwalt@gmail.com> Co-authored-by: Stefan van der Walt <stefanv@berkeley.edu>
1 parent 3d594e4 commit 874085d

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,16 @@ internal imports.
104104
Use `lazy.load` to lazily import external libraries:
105105

106106
```python
107-
linalg = lazy.load('scipy.linalg') # `linalg` will only be loaded when accessed
107+
sp = lazy.load('scipy') # `sp` will only be loaded when accessed
108+
sp.linalg.norm(...)
108109
```
109110

110-
You can also ask `lazy.load` to raise import errors as soon as it is called:
111+
_Note that lazily importing *sub*packages,
112+
i.e. `load('scipy.linalg')` will cause the package containing the
113+
subpackage to be imported immediately; thus, this usage is
114+
discouraged._
115+
116+
You can ask `lazy.load` to raise import errors as soon as it is called:
111117

112118
```
113119
linalg = lazy.load('scipy.linalg', error_on_import=True)

lazy_loader/__init__.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import os
1212
import sys
1313
import types
14+
import warnings
1415

1516
__all__ = ["attach", "load", "attach_stub"]
1617

@@ -121,34 +122,44 @@ def load(fullname, error_on_import=False):
121122
We often see the following pattern::
122123
123124
def myfunc():
124-
from numpy import linalg as la
125-
la.norm(...)
125+
import numpy as np
126+
np.norm(...)
126127
....
127128
128-
This is to prevent a module, in this case `numpy`, from being
129-
imported at function definition time, since that can be slow.
129+
Putting the import inside the function prevents, in this case,
130+
`numpy`, from being imported at function definition time.
131+
That saves time if `myfunc` ends up not being called.
130132
131-
This function provides a proxy module that, upon access, imports
133+
This `load` function returns a proxy module that, upon access, imports
132134
the actual module. So the idiom equivalent to the above example is::
133135
134-
la = lazy.load("numpy.linalg")
136+
np = lazy.load("numpy")
135137
136138
def myfunc():
137-
la.norm(...)
139+
np.norm(...)
138140
....
139141
140142
The initial import time is fast because the actual import is delayed
141143
until the first attribute is requested. The overall import time may
142144
decrease as well for users that don't make use of large portions
143-
of the library.
145+
of your library.
146+
147+
Warning
148+
-------
149+
While lazily loading *sub*packages technically works, it causes the
150+
package (that contains the subpackage) to be eagerly loaded even
151+
if the package is already lazily loaded.
152+
So, you probably shouldn't use subpackages with this `load` feature.
153+
Instead you should encourage the package maintainers to use the
154+
`lazy_loader.attach` to make their subpackages load lazily.
144155
145156
Parameters
146157
----------
147158
fullname : str
148159
The full name of the module or submodule to import. For example::
149160
150161
sp = lazy.load('scipy') # import scipy as sp
151-
spla = lazy.load('scipy.linalg') # import scipy.linalg as spla
162+
152163
error_on_import : bool
153164
Whether to postpone raising import errors until the module is accessed.
154165
If set to `True`, import errors are raised as soon as `load` is called.
@@ -165,6 +176,14 @@ def myfunc():
165176
except KeyError:
166177
pass
167178

179+
if "." in fullname:
180+
msg = (
181+
"subpackages can technically be lazily loaded, but it causes the "
182+
"package to be eagerly loaded even if it is already lazily loaded."
183+
"So, you probably shouldn't use subpackages with this lazy feature."
184+
)
185+
warnings.warn(msg, RuntimeWarning)
186+
168187
spec = importlib.util.find_spec(fullname)
169188
if spec is None:
170189
if error_on_import:

lazy_loader/tests/test_lazy_loader.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import importlib
12
import sys
23
import types
34

@@ -27,6 +28,16 @@ def test_lazy_import_basics():
2728
pass
2829

2930

31+
def test_lazy_import_subpackages():
32+
with pytest.warns(RuntimeWarning):
33+
hp = lazy.load("html.parser")
34+
assert "html" in sys.modules
35+
assert type(sys.modules["html"]) == type(pytest)
36+
assert isinstance(hp, importlib.util._LazyModule)
37+
assert "html.parser" in sys.modules
38+
assert sys.modules["html.parser"] == hp
39+
40+
3041
def test_lazy_import_impact_on_sys_modules():
3142
math = lazy.load("math")
3243
anything_not_real = lazy.load("anything_not_real")

0 commit comments

Comments
 (0)