import importlib.abc import sys import types class Module(types.ModuleType): pass class LazyProxy(importlib.abc.Loader): def __init__(self, loader): self.loader = loader def __call__(self, *args, **kwargs): self.args = args self.kwargs = kwargs return self def load_module(self, fullname): # XXX ignoring sys.modules details, e.g. if module already existed. lazy_module = LazyModule(fullname, proxy=self, name=fullname) sys.modules[fullname] = lazy_module return lazy_module class LazyModule(types.ModuleType): def __init__(self, *args, proxy, name, **kwargs): super().__init__(*args, **kwargs) self.__defaults = super().__dict__.copy() self.__proxy = proxy self.__name = name def __getattribute__(self, attr): # Stop using this custom __getattribute__. self.__class__ = Module current_state = self.__dict__.copy() # Make sure to not reset values back to their original state after loading. for key in self.__defaults.keys(): try: if current_state[key] is self.__defaults[key]: del current_state[key] except KeyError: pass loader = self.__proxy.loader(*self.__proxy.args, **self.__proxy.kwargs) try: loader.load_module(self.__name) except: try: # Unload as this isn't a reload (setting of __class__ guarantees this # won't be called in the reload case). del sys.modules[self.__name__] except KeyError: pass # Go back to failing. self.__class__ = LazyModule raise # Make sure any mutations are perpetuated. self.__dict__.update(current_state) return getattr(self, attr)