Skip to content

Warn and discourage lazy.load of subpackages #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,16 @@ internal imports.
Use `lazy.load` to lazily import external libraries:

```python
linalg = lazy.load('scipy.linalg') # `linalg` will only be loaded when accessed
sp = lazy.load('scipy') # `sp` will only be loaded when accessed
sp.linalg.norm(...)
```

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

You can ask `lazy.load` to raise import errors as soon as it is called:

```
linalg = lazy.load('scipy.linalg', error_on_import=True)
Expand Down
37 changes: 28 additions & 9 deletions lazy_loader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import os
import sys
import types
import warnings

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

Expand Down Expand Up @@ -121,34 +122,44 @@ def load(fullname, error_on_import=False):
We often see the following pattern::

def myfunc():
from numpy import linalg as la
la.norm(...)
import numpy as np
np.norm(...)
....

This is to prevent a module, in this case `numpy`, from being
imported at function definition time, since that can be slow.
Putting the import inside the function prevents, in this case,
`numpy`, from being imported at function definition time.
That saves time if `myfunc` ends up not being called.

This function provides a proxy module that, upon access, imports
This `load` function returns a proxy module that, upon access, imports
the actual module. So the idiom equivalent to the above example is::

la = lazy.load("numpy.linalg")
np = lazy.load("numpy")

def myfunc():
la.norm(...)
np.norm(...)
....

The initial import time is fast because the actual import is delayed
until the first attribute is requested. The overall import time may
decrease as well for users that don't make use of large portions
of the library.
of your library.

Warning
-------
While lazily loading *sub*packages technically works, it causes the
package (that contains the subpackage) to be eagerly loaded even
if the package is already lazily loaded.
So, you probably shouldn't use subpackages with this `load` feature.
Instead you should encourage the package maintainers to use the
`lazy_loader.attach` to make their subpackages load lazily.

Parameters
----------
fullname : str
The full name of the module or submodule to import. For example::

sp = lazy.load('scipy') # import scipy as sp
spla = lazy.load('scipy.linalg') # import scipy.linalg as spla

error_on_import : bool
Whether to postpone raising import errors until the module is accessed.
If set to `True`, import errors are raised as soon as `load` is called.
Expand All @@ -165,6 +176,14 @@ def myfunc():
except KeyError:
pass

if "." in fullname:
msg = (
"subpackages can technically be lazily loaded, but it causes the "
"package to be eagerly loaded even if it is already lazily loaded."
"So, you probably shouldn't use subpackages with this lazy feature."
)
warnings.warn(msg, RuntimeWarning)

spec = importlib.util.find_spec(fullname)
if spec is None:
if error_on_import:
Expand Down
11 changes: 11 additions & 0 deletions lazy_loader/tests/test_lazy_loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import importlib
import sys
import types

Expand Down Expand Up @@ -27,6 +28,16 @@ def test_lazy_import_basics():
pass


def test_lazy_import_subpackages():
with pytest.warns(RuntimeWarning):
hp = lazy.load("html.parser")
assert "html" in sys.modules
assert type(sys.modules["html"]) == type(pytest)
assert isinstance(hp, importlib.util._LazyModule)
assert "html.parser" in sys.modules
assert sys.modules["html.parser"] == hp


def test_lazy_import_impact_on_sys_modules():
math = lazy.load("math")
anything_not_real = lazy.load("anything_not_real")
Expand Down