Site Tools


python-import-hooks

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 ModuleSpec if found, None to 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_module runs module code in module's namespace

PathFinder (for sys.path entries):

  • Implement find_spec(name, path) for custom path entries
  • Return ModuleSpec or 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 spec
  • importlib.util.spec_from_file_location(name, path): create spec for file
  • importlib.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.modules before executing

Order matters:

  • Finders tried in order; first match wins
  • sys.meta_path is 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