Type Safety
pydftracer is fully type-checked and preserves function signatures when using decorators. This means you get full IDE autocomplete, type checking with mypy, and no loss of type information.
Overview
All pydftracer decorators are type-safe and signature-preserving:
✅ Full mypy compatibility
✅ IDE autocomplete works perfectly
✅ Type hints are preserved
✅ No
# type: ignoreneeded✅ Generic types supported
✅ ParamSpec for perfect signature preservation
Type-Preserving Decorators
Basic Example
The decorators do not change the type signature of your functions:
from dftracer.python import ai
import torch
from torch import Tensor
@ai.compute.forward
def forward(data: Tensor) -> Tensor:
return data
# Type is preserved! forward is still (Tensor) -> Tensor
result: Tensor = forward(torch.randn(10)) # ✅ Type checks correctly
# IDE autocomplete works perfectly
result.shape # ✅ IDE knows this is a Tensor
Without type preservation, you would lose this information and need:
# ❌ What happens with poorly-typed decorators
result = forward(torch.randn(10)) # type: ignore
# IDE doesn't know what type 'result' is
Complex Type Signatures
Even complex signatures are preserved:
from typing import List, Tuple, Optional, Dict
from dftracer.python import ai, dft_fn
@ai.data.preprocess
def process_batch(
images: List[Tensor],
labels: List[int],
augment: bool = True,
config: Optional[Dict[str, float]] = None
) -> Tuple[Tensor, Tensor]:
"""Process a batch of images and labels."""
# Implementation
batch_images = torch.stack(images)
batch_labels = torch.tensor(labels)
return batch_images, batch_labels
# Type signature is completely preserved
imgs, lbls = process_batch(
images=[torch.randn(3, 224, 224)],
labels=[1],
augment=True
)
# ✅ mypy knows: imgs is Tensor, lbls is Tensor
Generic Types
Generic types work perfectly:
from typing import TypeVar, List
from dftracer.python import ai
T = TypeVar('T')
@ai.data.item
def transform(item: T) -> T:
"""Identity transformation - preserves type."""
return item
# Works with any type
x: int = transform(5) # ✅ Returns int
y: str = transform("hello") # ✅ Returns str
z: Tensor = transform(torch.randn(10)) # ✅ Returns Tensor
Method Decorators
Class methods work perfectly too:
from dftracer.python import ai
import torch.nn as nn
class MyModel(nn.Module):
@ai.compute.forward
def forward(self, x: Tensor) -> Tensor:
# Type of 'self' is preserved
# Type of 'x' is preserved
# Return type is preserved
return self.layers(x)
model = MyModel()
# ✅ IDE autocomplete shows correct signature
output: Tensor = model.forward(torch.randn(10))
MyPy Integration
Configuration
pydftracer works out of the box with mypy. Add to your pyproject.toml:
[tool.mypy]
python_version = "3.9"
warn_return_any = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
# No special configuration needed for pydftracer!
Running MyPy
# Type check your code
mypy your_script.py
# Should pass without errors or type: ignore comments
✅ Success: no issues found in 1 source file
Example: Full Type Checking
from typing import List, Tuple
from dftracer.python import dftracer, ai, dft_fn
import torch
from torch import Tensor, nn, optim
class Model(nn.Module):
def __init__(self, input_size: int, hidden_size: int, output_size: int):
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, output_size)
@ai.compute.forward
def forward(self, x: Tensor) -> Tensor:
x = torch.relu(self.fc1(x))
return self.fc2(x)
@ai.device.transfer
def to_device(data: Tensor, device: str) -> Tensor:
return data.to(device)
@ai.pipeline.train
def train(
model: Model,
data: List[Tuple[Tensor, Tensor]],
optimizer: optim.Optimizer,
device: str,
epochs: int
) -> None:
for epoch in ai.pipeline.epoch.iter(range(epochs)):
for batch_idx, (images, labels) in ai.dataloader.fetch.iter(enumerate(data)):
# All types are preserved and checked
images = to_device(images, device)
labels = to_device(labels, device)
with ai.compute.forward:
output: Tensor = model(images)
with ai.compute.backward:
loss: Tensor = nn.functional.cross_entropy(output, labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
# ✅ This entire file type-checks with mypy with strict settings!
Common Patterns
Pattern 1: Preserving Generic Return Types
from typing import TypeVar, Callable
from dftracer.python import ai
T = TypeVar('T')
@ai.data.preprocess
def apply_transform(
data: T,
transform: Callable[[T], T]
) -> T:
return transform(data)
# ✅ Type is preserved through the generic
result: int = apply_transform(5, lambda x: x * 2)
Pattern 2: Multiple Return Values
from typing import Tuple
from dftracer.python import ai
@ai.data.preprocess
def split_batch(
batch: Tensor,
ratio: float = 0.8
) -> Tuple[Tensor, Tensor]:
split_point = int(len(batch) * ratio)
return batch[:split_point], batch[split_point:]
# ✅ Tuple unpacking is type-safe
train_data, val_data = split_batch(data)
# Both train_data and val_data are known to be Tensor
Pattern 3: Optional Parameters
from typing import Optional
from dftracer.python import ai
@ai.checkpoint.capture
def save_checkpoint(
model: nn.Module,
path: str,
metadata: Optional[Dict[str, str]] = None
) -> bool:
# Implementation
return True
# ✅ All these calls type-check correctly
save_checkpoint(model, "checkpoint.pt")
save_checkpoint(model, "checkpoint.pt", {"epoch": "10"})
save_checkpoint(model, "checkpoint.pt", None)
Benefits
Catch Errors Early
Type errors are caught before runtime:
@ai.compute.forward def forward(x: Tensor) -> Tensor: return x # ❌ mypy catches this error result: str = forward(torch.randn(10)) # error: Incompatible types in assignment
Better Documentation
Type hints serve as documentation:
@ai.data.preprocess def augment( image: Tensor, flip: bool = True, rotation: float = 0.0 ) -> Tensor: """ Type hints make it clear what this function expects and returns, even without reading the docstring! """ pass
Refactoring Confidence
Change function signatures with confidence:
# Change return type @ai.compute.forward def forward(x: Tensor) -> Tuple[Tensor, Tensor]: # Changed! return x, x # ✅ mypy will find all places that need updating result = forward(data) # error: Need to unpack tuple
IDE Productivity
Autocomplete knows exact types
Jump to definition works
Find usages is accurate
Refactoring is safe
Implementation Details
How It Works
pydftracer uses Python’s ParamSpec and TypeVar to preserve signatures:
from typing import TypeVar, ParamSpec, Callable
P = ParamSpec("P") # Captures parameters
R = TypeVar("R") # Captures return type
def decorator(func: Callable[P, R]) -> Callable[P, R]:
"""This decorator preserves the exact signature."""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
# Tracing logic here
return func(*args, **kwargs)
return wrapper
Comparison
Without type preservation:
# ❌ Poor decorator
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@bad_decorator
def forward(x: Tensor) -> Tensor:
return x
# Type checker doesn't know the signature anymore
result = forward(torch.randn(10)) # Unknown type
With type preservation (pydftracer):
# ✅ Good decorator (what pydftracer does)
from typing import ParamSpec, TypeVar, Callable
P = ParamSpec("P")
R = TypeVar("R")
def good_decorator(func: Callable[P, R]) -> Callable[P, R]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return func(*args, **kwargs)
return wrapper
@good_decorator
def forward(x: Tensor) -> Tensor:
return x
# ✅ Type checker knows exact signature
result: Tensor = forward(torch.randn(10))
Best Practices
Always Use Type Hints
# ✅ Good @ai.compute.forward def forward(x: Tensor) -> Tensor: return x # ❌ Missing type hints @ai.compute.forward def forward(x): return x
Use Strict MyPy Settings
[tool.mypy] disallow_untyped_defs = true disallow_incomplete_defs = true warn_return_any = true
Type Your Data Structures
from typing import List, Dict, NamedTuple class Sample(NamedTuple): image: Tensor label: int @ai.dataloader.fetch def load_batch(indices: List[int]) -> List[Sample]: return [Sample(load_image(i), get_label(i)) for i in indices]
Leverage Protocol Types
from typing import Protocol class Optimizer(Protocol): def step(self) -> None: ... def zero_grad(self) -> None: ... @ai.compute.step def optimizer_step(optimizer: Optimizer) -> None: optimizer.step() optimizer.zero_grad()
Summary
pydftracer is designed with type safety as a first-class feature:
Zero type information loss when using decorators
Full mypy compatibility with strict settings
IDE autocomplete works perfectly
Type stubs included for all modules
No ``# type: ignore`` needed in your code
ParamSpec-based implementation for perfect preservation
This means you can use pydftracer in production codebases with strict type checking requirements without any compromises!
Note
A Note on Internal Implementation
We’ve worked hard to make pydftracer’s public API fully type-safe for users. However,
internally, the implementation does use some # type: ignore comments and relaxed
mypy rules where necessary to handle the complexity of decorator internals, dynamic
profiler initialization, and Python’s type system limitations.
From your perspective as a user, your code will type-check perfectly without any workarounds. The internal complexity is hidden behind a clean, type-safe interface.
If you have ideas for improving pydftracer’s type safety—either in the public API or internal implementation—we’d love to hear from you! Please open an issue or discussion on GitHub.
For more examples, see Examples and AI/ML Tracing Guide.