Skip to content

Imports & Dependencies

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",
]
```sql

---

## 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

```python
# 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

-

  • [01 Packages & Organization](/05-py3/08-module-system/(01-packages-organization/) - Package structure
  • 00 Readme - Module system overview
  • [02 Execution Model & Compilation](/05-py3/09-bytecode-and-execution/(02-execution-model-compilation/) - How modules are loaded