Imports & Dependencies: Managing Code Reuse¶
Overview¶
Import system manages code reuse: - Import mechanisms: Different ways to load modules - Circular imports: Common pitfall - sys.modules: Runtime module cache - importlib: Dynamic imports
Import Mechanics¶
Three Import Styles¶
# 1. Import module
import os
print(os.path.exists('/tmp'))
# 2. Import specific names
from os.path import exists
print(exists('/tmp'))
# 3. Import with alias
import numpy as np
arr = np.array([1, 2, 3])
Absolute vs Relative Imports¶
# myproject/models/neural_net.py
# Absolute imports (preferred)
from myproject.core import BaseModel
from myproject.utils.math import normalize
# Relative imports (within package)
from ..core import BaseModel # One level up
from ..utils.math import normalize
from .layers import Dense # Same directory
Circular Imports¶
The Problem¶
# module_a.py
from module_b import b_function
def a_function():
return b_function()
# module_b.py
from module_a import a_function # ERROR: Circular import
def b_function():
return a_function()
Solutions¶
Solution 1: Import at function level
# module_a.py
def a_function():
from module_b import b_function # Import when needed
return b_function()
# module_b.py
def b_function():
from module_a import a_function # Import when needed
return a_function()
Solution 2: Restructure code
# core.py - shared functionality
def common_function():
return 42
# module_a.py
from core import common_function
# module_b.py
from core import common_function
# No circular dependency!
Dynamic Imports¶
importlib Usage¶
import importlib
# Import module by string
module = importlib.import_module('os.path')
print(module.exists('/tmp'))
# Import with dynamic name
module_name = 'json'
json_module = importlib.import_module(module_name)
# Reload module
importlib.reload(json_module)
Plugin System¶
import importlib
import pkgutil
from pathlib import Path
def load_plugins(plugin_dir):
"""Dynamically load all plugins from directory."""
plugins = {}
for importer, modname, ispkg in pkgutil.iter_modules([plugin_dir]):
try:
module = importlib.import_module(f"plugins.{modname}")
if hasattr(module, 'Plugin'):
plugins[modname] = module.Plugin()
except ImportError as e:
print(f"Failed to load plugin {modname}: {e}")
return plugins
# plugins/my_plugin.py
class Plugin:
def execute(self):
return "My plugin output"
# Load all plugins
plugins = load_plugins('plugins')
print(plugins['my_plugin'].execute())
sys.modules Cache¶
Understanding Module Cache¶
import sys
# All loaded modules are in sys.modules
print('os' in sys.modules) # True
print('sys' in sys.modules) # True
# Importing loads from cache
import os # Returns cached module (fast)
# Force reload
import importlib
importlib.reload(os) # Reloads from disk
# Remove from cache
del sys.modules['os']
import os # Reloads from disk
Managing Dependencies¶
requirements.txt¶
numpy==1.21.0
torch>=1.9.0,<2.0
pandas>=1.3.0
pyproject.toml (Modern)¶
[project]
name = "myproject"
version = "0.1.0"
dependencies = [
"numpy>=1.21.0",
"torch>=1.9.0",
"pandas>=1.3.0",
]
[project.optional-dependencies]
dev = [
"pytest>=6.0",
"mypy>=0.900",
]
ml = [
"scikit-learn>=0.24",
]
Best Practices¶
Do's¶
✓ Use absolute imports (within packages)
✓ Organize imports: std lib, third-party, local
✓ Import at module level (except in functions for circular imports)
✓ Use __all__ to define public API
Don'ts¶
✗ Use star imports (from module import *)
✗ Create circular dependencies
✗ Import at module level if it causes circular import
✗ Import side-effects (like prints on import)
Real-World ML Example¶
Feature Factory with Dynamic Imports¶
# features/__init__.py
import importlib
from pathlib import Path
def load_features():
"""Load all feature modules."""
features = {}
for path in Path(__file__).parent.glob("*.py"):
if path.name.startswith("_"):
continue
module_name = path.stem
try:
module = importlib.import_module(f".{module_name}", package=__name__)
if hasattr(module, "Feature"):
features[module_name] = module.Feature()
except ImportError:
pass
return features
# features/numeric.py
class Feature:
name = "numeric_features"
def extract(self, data):
return data.select_dtypes(include=['number'])
# features/categorical.py
class Feature:
name = "categorical_features"
def extract(self, data):
return data.select_dtypes(include=['object'])
# Usage
features = load_features()
for name, feature in features.items():
print(f"Loaded: {name}")
Summary: Import Patterns¶
| Pattern | Use Case |
|---|---|
| Absolute imports | Within packages (preferred) |
| Relative imports | Same package communication |
| Dynamic imports | Plugin systems, CLIs |
| Late imports | Breaking circular dependencies |
Related Topics¶
- 01 Packages & Organization - Package structure
- 00 Readme - Module system overview
- 02 Execution Model & Compilation - How modules are loaded