11import importlib
22import importlib .util
3+ import types
34import os
45import sys
56
@@ -78,7 +79,7 @@ def __dir__():
7879 return __getattr__ , __dir__ , list (__all__ )
7980
8081
81- def load (fullname ):
82+ def load (fullname , error_on_import = False ):
8283 """Return a lazily imported proxy for a module.
8384
8485 We often see the following pattern::
@@ -112,6 +113,9 @@ def myfunc():
112113
113114 sp = lazy.load('scipy') # import scipy as sp
114115 spla = lazy.load('scipy.linalg') # import scipy.linalg as spla
116+ error_on_import : bool
117+ Whether to postpone raising import errors until the module is accessed.
118+ If set to `True`, import errors are raised as soon as `load` is called.
115119
116120 Returns
117121 -------
@@ -127,7 +131,16 @@ def myfunc():
127131
128132 spec = importlib .util .find_spec (fullname )
129133 if spec is None :
130- raise ModuleNotFoundError (f"No module name '{ fullname } '" )
134+ if error_on_import :
135+ raise ModuleNotFoundError (f"No module named '{ fullname } '" )
136+ else :
137+ spec = importlib .util .spec_from_loader (fullname , loader = None )
138+ module = importlib .util .module_from_spec (spec )
139+ tmp_loader = importlib .machinery .SourceFileLoader (module , path = None )
140+ loader = DelayedImportErrorLoader (tmp_loader )
141+ loader .exec_module (module )
142+ # dont add to sys.modules. The module wasn't found.
143+ return module
131144
132145 module = importlib .util .module_from_spec (spec )
133146 sys .modules [fullname ] = module
@@ -136,3 +149,22 @@ def myfunc():
136149 loader .exec_module (module )
137150
138151 return module
152+
153+
154+ class DelayedImportErrorLoader (importlib .util .LazyLoader ):
155+ def exec_module (self , module ):
156+ super ().exec_module (module )
157+ module .__class__ = DelayedImportErrorModule
158+
159+
160+ class DelayedImportErrorModule (types .ModuleType ):
161+ def __getattribute__ (self , attr ):
162+ """Trigger a ModuleNotFoundError upon attribute access"""
163+ spec = super ().__getattribute__ ("__spec__" )
164+ # allows isinstance and type functions to work without raising error
165+ if attr in ["__class__" ]:
166+ return super ().__getattribute__ ("__class__" )
167+
168+ raise ModuleNotFoundError (
169+ f"No module named '{ spec .name } '"
170+ )
0 commit comments