# Structure Reference

## Import Profiling Tools

```bash
# Built-in import time profiling (cumulative + self time per module):
$RUNNER -X importtime -c "import mypackage" 2>&1 | head -30

# Sort by self time (most expensive individual imports):
$RUNNER -X importtime -c "import mypackage" 2>&1 | sort -t'|' -k1 -rn | head -20

# Profile WHERE time is spent inside a slow import:
$RUNNER -m cProfile -s cumtime -c "import mypackage" 2>&1 | head -40
```

Additional tools:
- **tuna**: `$RUNNER -X importtime -c "import mypackage" 2> /tmp/import.log && tuna /tmp/import.log` — browser-based interactive visualization of import tree
- **py-spy**: `py-spy record -o /tmp/import.svg -- $RUNNER -c "import mypackage"` — flamegraph showing import-time call stacks

## Lazy Import Patterns

### `importlib.import_module()` — defer heavy imports to first use

```python
# Before: numpy loaded at import time even if rarely needed
import numpy as np

def transform(data):
    return np.array(data)

# After: numpy loaded only when transform() is first called
def transform(data):
    import numpy as np
    return np.array(data)

# Or with importlib for dynamic module names:
import importlib

def get_backend(name):
    mod = importlib.import_module(f"mypackage.backends.{name}")
    return mod.Backend()
```

### Module-level `__getattr__` (PEP 562) — lazy attribute access

```python
# mypackage/__init__.py
_lazy_imports = {
    "heavy_module": "mypackage.heavy_module",
    "HeavyClass": "mypackage.heavy_module.HeavyClass",
}

def __getattr__(name):
    if name in _lazy_imports:
        import importlib
        mod = importlib.import_module(_lazy_imports[name].rsplit(".", 1)[0])
        obj = getattr(mod, name, mod)
        globals()[name] = obj  # cache for subsequent access
        return obj
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
```

### Conditional imports — optional dependencies

```python
try:
    import orjson as json
except ImportError:
    import json
```

### TYPE_CHECKING guard — type-only imports

```python
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from mypackage.heavy import HeavyClass  # zero runtime cost

def process(obj: HeavyClass) -> None: ...
```

## Barrel Import Antipattern

A `__init__.py` that eagerly re-exports everything forces the entire subtree to load.

```python
# BAD: mypackage/__init__.py — imports entire package tree at first touch
from .models import *
from .pipeline import *
from .utils import *

# FIX option 1: explicit __all__ with lazy __getattr__
__all__ = ["Model", "Pipeline", "normalize"]

def __getattr__(name):
    if name == "Model":
        from .models import Model
        return Model
    # ...
    raise AttributeError(name)

# FIX option 2: let users import submodules directly
# mypackage/__init__.py is empty or minimal
# Users write: from mypackage.models import Model
```

## Import-Time Computation Antipattern

Module-level code that runs expensive operations on import:

```python
# BAD: runs on import — DB connection, file I/O, heavy regex
import re
PATTERN = re.compile("|".join(open("patterns.txt").read().splitlines()))
DB = connect_to_database()
REGISTRY = {cls.__name__: cls for cls in scan_all_plugins()}
```

**Detection**: `-X importtime` shows the module is slow. `cProfile` of the import reveals which calls inside the module are expensive.

```python
# FIX: defer to first access
import functools

@functools.cache
def get_pattern():
    import re
    return re.compile("|".join(open("patterns.txt").read().splitlines()))

@functools.cache
def get_db():
    return connect_to_database()
```

## Static Analysis Patterns

```bash
# Barrel imports (star re-exports in __init__.py):
grep -rn "from .* import \*" --include="__init__.py"

# Module-level function calls (potential import-time computation):
grep -rn "^[a-zA-Z_].*=.*(" --include="*.py" | grep -v "def \|class \|#\|import "

# Heavy imports often deferred (candidates for lazy loading):
grep -rn "^import \(numpy\|pandas\|torch\|tensorflow\|scipy\)" --include="*.py"
grep -rn "^from \(numpy\|pandas\|torch\|tensorflow\|scipy\)" --include="*.py"

# Circular import candidates (A imports B, B imports A):
# Build the cross-module call matrix from analysis-methodology.md, then check
# for bidirectional edges — those are circular dependency risks.
```

## Micro-Benchmark Template

> See `../shared/micro-benchmark.md` for the general A/B pattern.

**Structure-specific**: Measure import time with `sys.modules` cache clearing between runs:

```python
# /tmp/bench_import_time.py
import timeit, sys

PACKAGE = "mypackage"

def clear_cache():
    for mod in list(sys.modules):
        if mod.startswith(PACKAGE):
            del sys.modules[mod]

def bench_import():
    clear_cache()
    __import__(PACKAGE)

if __name__ == "__main__":
    n = 10
    t = timeit.timeit(bench_import, number=n)
    print(f"Import time: {t/n:.4f}s avg over {n} runs")
```

Run before/after applying lazy imports. A meaningful improvement is >20% reduction in import time.
