251 lines
5.1 KiB
Markdown
251 lines
5.1 KiB
Markdown
# Code Style Rule
|
|
|
|
Enforce code style standards for ML projects to ensure maintainability and consistency.
|
|
|
|
## Core Principles
|
|
|
|
### Small File Principle (200-400 lines)
|
|
|
|
- Keep each file within 200-400 lines
|
|
- Split into multiple modules when exceeding 400 lines
|
|
- Organize related functionality under the same directory
|
|
|
|
**Example structure:**
|
|
```
|
|
src/model_module/
|
|
├── brain_decoder/
|
|
│ ├── __init__.py # Factory & Registry (50 lines)
|
|
│ ├── base_model.py # Base class (200 lines)
|
|
│ ├── transformer.py # Transformer impl (300 lines)
|
|
│ └── cnn.py # CNN impl (250 lines)
|
|
```
|
|
|
|
### Immutability First
|
|
|
|
- Use dataclass for configuration (immutable)
|
|
- Avoid mutating input parameters inside functions
|
|
- Use `@dataclass(frozen=True)` to ensure config immutability
|
|
|
|
```python
|
|
from dataclasses import dataclass
|
|
|
|
@dataclass(frozen=True)
|
|
class ModelConfig:
|
|
hidden_dim: int
|
|
num_layers: int
|
|
dropout: float = 0.1
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
- Use try/except for exception handling
|
|
- Catch specific exception types, avoid bare except
|
|
- Log error information for debugging
|
|
|
|
```python
|
|
try:
|
|
data = load_data(path)
|
|
except FileNotFoundError as e:
|
|
logger.error(f"Data file not found: {path}")
|
|
raise
|
|
```
|
|
|
|
### Type Hints
|
|
|
|
- All functions must have type hints
|
|
- Use types from the typing module
|
|
- Use TypeVar for complex types
|
|
|
|
```python
|
|
from typing import Dict, List, Optional, TypeVar
|
|
|
|
T = TypeVar('T', bound=Dataset)
|
|
|
|
def process_data(data: List[Dict], config: Config) -> Optional[DataFrame]:
|
|
...
|
|
```
|
|
|
|
## Python Specific Standards
|
|
|
|
### Import Order
|
|
|
|
```python
|
|
# 1. Standard library
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# 2. Third-party libraries
|
|
import torch
|
|
import numpy as np
|
|
from hydra import compose, initialize
|
|
|
|
# 3. Local modules
|
|
from src.data_module import DataLoader
|
|
from src.model_module import Model
|
|
```
|
|
|
|
### Naming Conventions
|
|
|
|
```python
|
|
# Class names: PascalCase
|
|
class DataLoader:
|
|
pass
|
|
|
|
# Functions/variables: snake_case
|
|
def load_config():
|
|
batch_size = 32
|
|
|
|
# Constants: UPPER_SNAKE_CASE
|
|
MAX_EPOCHS = 100
|
|
DEFAULT_LR = 0.001
|
|
|
|
# Private: underscore prefix
|
|
def _internal_function():
|
|
pass
|
|
```
|
|
|
|
### Docstrings
|
|
|
|
```python
|
|
def train_model(cfg: Config) -> Model:
|
|
"""Train the model.
|
|
|
|
Args:
|
|
cfg: Training configuration object.
|
|
|
|
Returns:
|
|
Trained model instance.
|
|
|
|
Raises:
|
|
ValueError: When configuration is invalid.
|
|
"""
|
|
...
|
|
```
|
|
|
|
## ML Project Specific Standards
|
|
|
|
### Factory & Registry Pattern
|
|
|
|
All modules must use factory and registry patterns:
|
|
|
|
```python
|
|
# dataset/__init__.py
|
|
DATASET_FACTORY: Dict[str, Type[Dataset]] = {}
|
|
|
|
def register_dataset(name: str):
|
|
def decorator(cls):
|
|
DATASET_FACTORY[name] = cls
|
|
return cls
|
|
return decorator
|
|
|
|
def DatasetFactory(name: str) -> Type[Dataset]:
|
|
return DATASET_FACTORY.get(name, SimpleDataset)
|
|
```
|
|
|
|
### Config-Driven Models
|
|
|
|
Model `__init__` should only accept a `cfg` parameter:
|
|
|
|
```python
|
|
@register_model('MyModel')
|
|
class MyModel(nn.Module):
|
|
def __init__(self, cfg: Config):
|
|
super().__init__()
|
|
# All hyperparameters from cfg
|
|
self.hidden_dim = cfg.model.hidden_dim
|
|
```
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
run/
|
|
├── conf/ # Hydra configs
|
|
├── pipeline/ # Workflow scripts
|
|
└── outputs/ # Output directory
|
|
|
|
src/
|
|
├── data_module/ # Data module
|
|
│ ├── dataset/
|
|
│ ├── augmentation/
|
|
│ └── utils.py
|
|
├── model_module/ # Model module
|
|
├── trainer_module/ # Trainer module
|
|
└── utils/ # Shared utilities
|
|
```
|
|
|
|
## Prohibited Patterns
|
|
|
|
❌ **Prohibited:**
|
|
- Files exceeding 800 lines
|
|
- Nesting deeper than 4 levels
|
|
- Mutable default arguments: `def foo(a=[]):`
|
|
- Global variables (use config instead)
|
|
- Bare except: `except:`
|
|
- Hardcoded hyperparameters (use cfg)
|
|
- Unused imports
|
|
- print() debug statements (use logger)
|
|
|
|
✅ **Recommended:**
|
|
- Split large files
|
|
- Use early returns to reduce nesting
|
|
- `def foo(a=None):`
|
|
- Config-driven parameters
|
|
- Specific exception catching
|
|
- Type hints
|
|
- Docstrings
|
|
- Logger for logging
|
|
|
|
## Verification Checklist
|
|
|
|
Before committing code, ensure:
|
|
|
|
```bash
|
|
# Type checking
|
|
mypy src/
|
|
|
|
# Code style
|
|
ruff check .
|
|
|
|
# Tests
|
|
pytest
|
|
```
|
|
|
|
Violations of these rules will be flagged by the code-reviewer agent.
|
|
|
|
## Logging Standards
|
|
|
|
### Logger Naming
|
|
|
|
```python
|
|
import logging
|
|
|
|
# Use module-level logger with __name__
|
|
logger = logging.getLogger(__name__)
|
|
```
|
|
|
|
### Log Levels
|
|
|
|
| Level | Usage |
|
|
|-------|-------|
|
|
| `DEBUG` | Detailed diagnostic info (tensor shapes, config values) |
|
|
| `INFO` | Training progress, epoch results, key milestones |
|
|
| `WARNING` | Recoverable issues (fallback behavior, deprecation) |
|
|
| `ERROR` | Failures that need attention but don't crash |
|
|
| `CRITICAL` | Unrecoverable errors |
|
|
|
|
## Module `__init__.py` Standards
|
|
|
|
Every package `__init__.py` must define `__all__` for explicit public API:
|
|
|
|
```python
|
|
# src/data_module/__init__.py
|
|
from .dataset import DatasetFactory, register_dataset
|
|
from .augmentation import AugmentationFactory
|
|
|
|
__all__ = [
|
|
"DatasetFactory",
|
|
"register_dataset",
|
|
"AugmentationFactory",
|
|
]
|
|
```
|