docs(styleguide): add python.md §17.9 (ban local imports + _PREFIX aliasing + repeated from_dict)
This commit is contained in:
@@ -333,6 +333,85 @@ The ONLY place these patterns are allowed is at the literal wire boundary — th
|
||||
- The new `boundary_layer` audit (planned in `conductor/tracks/cruft_elimination_20260627/spec.md`) — documents every `Metadata` usage with justification
|
||||
- Pre-commit: every commit MUST pass all three audits above
|
||||
|
||||
### 17.9 Banned: Local imports + aliasing-for-naming-convenience + repeated `from_dict()` (Added 2026-06-27)
|
||||
|
||||
**LLMs default to local imports with `as _PREFIX` aliasing.** This is the "I don't want to repeat the long name" pattern. It's banned. Local imports add overhead; aliasing hides intent; repeated `.from_dict()` calls in the same expression are wasteful.
|
||||
|
||||
**17.9a — Banned: Local imports inside functions**
|
||||
|
||||
```python
|
||||
# BANNED:
|
||||
def calculate_total(app):
|
||||
from src.type_aliases import MMAUsageStats as _MMA # ← local import; defeats static analysis
|
||||
return sum(_MMA.from_dict(u).model for u in app.mma_tier_usage.values())
|
||||
|
||||
# CORRECT:
|
||||
# Add the import at the top of the module:
|
||||
# from src.type_aliases import MMAUsageStats
|
||||
|
||||
def calculate_total(app):
|
||||
return sum(u.model for u in app.mma_tier_usage.values())
|
||||
```
|
||||
|
||||
**Why:** local imports:
|
||||
- Add per-call import overhead (cached after first call, but still pollutes the namespace).
|
||||
- Defeat static analysis (ruff/mypy can't see what's imported where).
|
||||
- Hide dependencies (a reader has to scroll to find what's actually used).
|
||||
- Encourage the aliasing anti-pattern (see 17.9b).
|
||||
|
||||
The ONLY exception: local imports inside `try/except ImportError` blocks for optional dependencies. Even then, prefer lazy module-level imports (`_module = None` then `global _module; _module = importlib.import_module(...)`).
|
||||
|
||||
**17.9b — Banned: `import X as _X` aliasing-for-naming-convenience**
|
||||
|
||||
```python
|
||||
# BANNED:
|
||||
from src.type_aliases import MMAUsageStats as _MMA
|
||||
from src.openai_schemas import ToolCall as _TC
|
||||
from src.models import FileItem as _FI
|
||||
|
||||
# CORRECT:
|
||||
from src.type_aliases import MMAUsageStats
|
||||
from src.openai_schemas import ToolCall
|
||||
from src.models import FileItem
|
||||
```
|
||||
|
||||
**Why:** `_PREFIX` aliasing is "I don't want to repeat the long name, so I'll shorten it." But the long name IS the documentation — `MMAUsageStats` tells you what it is; `_MMA` is opaque. The "long name" is rarely actually long enough to justify aliasing. If you find yourself aliasing to shorten, the real problem is the function is too long — extract.
|
||||
|
||||
**17.9c — Banned: Repeated `.from_dict()` calls in the same expression**
|
||||
|
||||
```python
|
||||
# BANNED:
|
||||
from src.type_aliases import MMAUsageStats as _MMA
|
||||
total_cost = sum(cost_tracker.estimate_cost(
|
||||
_MMA.from_dict(u).model or 'unknown',
|
||||
_MMA.from_dict(u).input,
|
||||
_MMA.from_dict(u).output,
|
||||
) for u in app.mma_tier_usage.values())
|
||||
|
||||
# CORRECT:
|
||||
total_cost = sum(cost_tracker.estimate_cost(
|
||||
stats.model or 'unknown',
|
||||
stats.input,
|
||||
stats.output,
|
||||
) for stats in (
|
||||
MMAUsageStats.from_dict(u) if isinstance(u, dict) else u
|
||||
for u in app.mma_tier_usage.values()
|
||||
))
|
||||
```
|
||||
|
||||
**Why:** repeated `.from_dict()` calls:
|
||||
- Waste work (parse the same dict multiple times).
|
||||
- Indicate a broken design (the variable's type isn't right).
|
||||
- Should be cached in a local variable OR the type should be promoted at the boundary so `from_dict()` isn't called at the consumer site at all.
|
||||
|
||||
The CORRECT pattern (preferred): promote the type at the boundary. After `cruft_elimination_20260627`, `app.mma_tier_usage` is typed `dict[str, MMAUsageStats]` (the boundary does `from_dict()` ONCE). The consumer iterates `stats.model`, `stats.input`, `stats.output` directly. No `from_dict()` at the consumer site.
|
||||
|
||||
### 17.10 Enforcement (LLM-default anti-patterns)
|
||||
|
||||
- Pre-commit: every commit MUST pass ruff with the project's configured lint set (`pyproject.toml [tool.ruff.lint]`).
|
||||
- Tier 2 review: reject any commit that adds a local import or `_PREFIX` alias.
|
||||
- The static analysis script `scripts/audit_imports.py` (planned) flags local imports outside `try/except ImportError` blocks.
|
||||
|
||||
## 18. See Also — Per-File Pattern Demonstrations
|
||||
|
||||
The following per-source-file guides show these conventions applied in real code:
|
||||
|
||||
Reference in New Issue
Block a user