I’m trying to implement an import hook / custom loading machinery that applies to as many modules as possible, including whichever is loaded as __main__. Specifically, I need to be able to control exec_module when loading __main__, but I haven’t been able to get anything working. Here are the scenarios I’m interested in:
- When invoking a script directly:
python foo.py - When invoking a script with runpy:
python -m foo
Is the import hook machinery designed to be able to handle this case? I haven’t been able to find any documentation or discussions about it, and I’d really appreciate any advice or background knowledge. Thanks in advance!
Example
Python 3.13.4
directory structure:
.
├── bar.py
├── foo.py
└── sitecustomize.py
sitecustomize.py:
import sys
import importlib.abc
import importlib.machinery
class MyLoader(importlib.abc.Loader):
def __init__(self, loader):
self.loader = loader
def __getattr__(self, name):
return getattr(self.loader, name)
def create_module(self, spec):
return None
def exec_module(self, module):
print(f"MyLoader: loading {module.__name__}")
return self.loader.exec_module(module)
class MyFinder(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
print(f"MyFinder: finding {fullname}")
for finder in sys.meta_path:
if finder is self:
continue
spec = finder.find_spec(fullname, path, target)
if spec and spec.loader:
spec.loader = MyLoader(spec.loader)
return spec
return None
sys.meta_path.insert(0, MyFinder())
foo.py:
print("in foo.py")
import bar
bar.py:
print("in bar.py")
Procedure
Run PYTHONPATH=. python foo.py. Expected output:
in foo.py
MyFinder: finding bar
MyLoader: loading bar
in bar.py
In this case, bar loading works as expected, but neither MyFinder nor MyLoader is invoked for foo.
Now run PYTHONPATH=. python -m foo. Expected output:
MyFinder: finding runpy
MyLoader: loading runpy
MyFinder: finding foo
in foo.py
MyFinder: finding bar
MyLoader: loading bar
in bar.py
This is slightly more interesting; MyFinder is invoked for foo, but not MyLoader.