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):

Loader (loads modules):

PathFinder (for sys.path entries):

Use cases:

Finding and spec creation:

Module execution:

Import interception:

Order matters:

Performance considerations: