# foo.py
def __getattr__(name):
# clean up the lazy loader before loading
# this way it's cleaned up if the implementation doesn't replace it,
# and not scrubbed if it does
global __getattr__
del __getattr__
import sys
self = sys.modules[__name__]
from . import _foo
# "star-import" by adding names that show up in __dir__
self.__dict__.update(**{k: getattr(_foo, k) for k in _foo.__dir__()})
# On future attribute lookups, everything will be in place.
# But this time, we need to delegate to the normal lookup explicitly
return getattr(self, name)
It can just implement lazy loading itself today, by using module-level __getattr__ (https://docs.python.org/3/reference/datamodel.html#customizi...) to overwrite itself with a private implementation module at the appropriate time. Something like:
Genericizing this is left as an exercise.