python-import-hooks
Table of Contents
Python import hooks
Python import hooks let you customize how modules are found and loaded. Using sys.meta_path, sys.path_hooks, and the importlib module, you can intercept imports to load from non-standard locations (zip files, URLs, generated code), transform source before execution, or enforce module restrictions.
Use import hooks for plugin systems, lazy-loading, compressed package distribution, or transparent source transformation.
Example
This example shows import hooks intercepting module loads.
# run: python3 import_hooks.py # description: customizing module loading with import hooks import sys import importlib.abc import importlib.machinery from importlib.util import spec_from_file_location, module_from_spec # Simple import hook: transform module names class DashToUnderscoreFinder(importlib.abc.MetaPathFinder): """Allow importing with dashes instead of underscores.""" def find_spec(self, fullname, path, target=None): # Only handle top-level modules we care about if '-' not in fullname: return None # Try importing with underscores canonical_name = fullname.replace('-', '_') try: return importlib.util.find_spec(canonical_name) except (ImportError, AttributeError): return None # Register the finder sys.meta_path.insert(0, DashToUnderscoreFinder()) # Example usage (would work if modules existed) # import some-module # would import some_module instead # Logging hook: track all imports class ImportLogger(importlib.abc.MetaPathFinder): """Log all import attempts.""" def find_spec(self, fullname, path, target=None): print(f"Importing: {fullname}") return None # delegate to next finder sys.meta_path.append(ImportLogger()) print("Importing os and sys:") import os import sys print("\n" + "="*60 + "\n") # Version-specific imports import builtins original_import = builtins.__import__ def version_aware_import(name, *args, **kwargs): """Allow version-specific modules.""" if 'v2' in name: print(f"Loading v2 module: {name}") return original_import(name, *args, **kwargs) # Don't actually override (would break things) # builtins.__import__ = version_aware_import # Transforming source code class TransformingLoader(importlib.abc.Loader): def exec_module(self, module): # Could transform source here print(f"Transforming module: {module.__name__}") # Path hook: custom file system class CustomPathFinder: """Handle custom path entries.""" def find_spec(self, name): return None # Not implemented # sys.path_hooks.append(CustomPathFinder) # Lazy import example class LazyImporter(importlib.abc.MetaPathFinder): def find_spec(self, fullname, path, target=None): if fullname.startswith('lazy_'): print(f"LazyImporter: would load {fullname} lazily") return None sys.meta_path.append(LazyImporter()) # Test lazy loader import lazy_module # would trigger lazy importer
Common patterns
MetaPathFinder (finds modules):
- Implement
find_spec(fullname, path, target) - Return
ModuleSpecif found,Noneto let others try - Called in order added to
sys.meta_path
Loader (loads modules):
- Implement
exec_module(module)to execute code - Implement
create_module(spec)to customize module creation (usually None) - Loader's
exec_moduleruns module code in module's namespace
PathFinder (for sys.path entries):
- Implement
find_spec(name, path)for custom path entries - Return
ModuleSpecor None - Attached to
sys.path_hooks
Use cases:
- Loading modules from zip files (built-in, via
zipimport) - Loading from remote URLs
- Transforming source code (e.g., transpiling, auto-formatting)
- Restricting which modules can be imported
- Lazy-loading modules on first access
- Plugin systems (dynamic module loading)
Finding and spec creation:
importlib.util.find_spec(name): find module specimportlib.util.spec_from_file_location(name, path): create spec for fileimportlib.util.module_from_spec(spec): create module object
Module execution:
spec.loader.exec_module(module): load module code into namespace- Code executes with
module.__dict__as namespace - Module attributes become module's attributes
Import interception:
sys.modules[name]: dict of already-imported modules- Check here first before searching; can pre-populate to provide mock modules
- Import loop prevention: add to
sys.modulesbefore executing
Order matters:
- Finders tried in order; first match wins
sys.meta_pathis checked before built-in finders- Insert at position 0 for highest priority
Performance considerations:
- Finders are called for every import
- Return None quickly if not responsible
- Cache results to avoid repeated work
- Late imports have performance cost
python-import-hooks.md · Last modified: by 127.0.0.1
