Skip to content

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