11from collections .abc import Sequence
22import importlib
3- from importlib .abc import MetaPathFinder
3+ from importlib .abc import Loader
44from importlib .machinery import ModuleSpec , PathFinder , SourceFileLoader
55from itertools import chain
66import re
@@ -71,18 +71,32 @@ def __reduce__(self):
7171
7272
7373class LazyModuleLoader (SourceFileLoader ):
74+ def __init__ (
75+ self , fullname : str , path : str , origin_loader : Optional [Loader ] = None
76+ ) -> None :
77+ super ().__init__ (fullname , path )
78+
79+ self .origin_loader = origin_loader
80+
7481 def create_module (self , spec : ModuleSpec ) -> Optional [ModuleType ]:
7582 if self .name in sys .modules :
7683 return sys .modules [self .name ]
84+
85+ # create a simple empty lazy module
7786 module = LazyModule (spec .name )
7887 # pre-initialize the module to avoid infinite recursion
7988 module .__lazy_vars_validated__ = None
8089 module .__lazy_vars_mapping__ = {}
8190 return module
8291
8392 def exec_module (self , module : ModuleType ) -> None :
84- super ().exec_module (module )
93+ # execute the module code
94+ if self .origin_loader is not None :
95+ self .origin_loader .exec_module (module )
96+ else :
97+ super ().exec_module (module )
8598
99+ # initialize the module's lazy vars structure
86100 if (
87101 isinstance (module , LazyModule )
88102 and getattr (module , "__lazy_vars_validated__" , None ) is None
@@ -104,19 +118,27 @@ def exec_module(self, module: ModuleType) -> None:
104118 )
105119
106120
107- class LazyModuleFinder (MetaPathFinder ):
121+ class LazyModuleFinder (PathFinder ):
122+ @classmethod
108123 def find_spec (
109- self ,
124+ cls ,
110125 fullname : str ,
111- path : Optional [Sequence [str ]],
126+ path : Optional [Sequence [str ]] = None ,
112127 target : Optional [ModuleType ] = None ,
113128 ) -> Optional [ModuleSpec ]:
129+ # match if the module should be loaded lazily
114130 if any (re .match (pattern , fullname ) for pattern in LAZY_MODULES ):
115- module_spec = PathFinder .find_spec (fullname , path , target )
131+ # find the module spec
132+ module_spec = super ().find_spec (fullname , path , target )
133+ # do nothing if spec is not found
116134 if not module_spec or not module_spec .origin :
117135 return
118136
119- module_spec .loader = LazyModuleLoader (module_spec .name , module_spec .origin )
137+ # wraps the module's loader to change the module into LazyModule
138+ # NOTE: module may have custom loader in some environment (e.g. pyinstaller)
139+ module_spec .loader = LazyModuleLoader (
140+ module_spec .name , module_spec .origin , module_spec .loader
141+ )
120142 return module_spec
121143
122144
0 commit comments