# Leveraging Python Decorators for Elegant Type Inference
Python offers multiple approaches for creating flexible, type-safe code. While class inheritance is a common method, decorators provide a compelling alternative because of automatic type inference.
Let's compare class inheritance and decorators for type inference using a grade calculator example. This calculator converts numerical scores to either numeric grades (0-10 scale) or letter grades (A-F).
## Class Inheritance Approach
This method uses a base class with generic types and subclasses for specific implementations:
```python
from typing import Generic, TypeVar
T = TypeVar('T')
U = TypeVar('U')
class GradeCalculator(Generic[T, U]):
def calculate_grade(self, score: T) -> U:
raise NotImplementedError
def describe_grade(self, grade: U) -> str:
return f"The calculated grade is: {grade}"
class NumericGradeCalculator(GradeCalculator[float, float]):
def calculate_grade(self, score: float) -> float:
return round(score / 10, 1)
class LetterGradeCalculator(GradeCalculator[int, str]):
def calculate_grade(self, score: int) -> str:
if score >= 90: return "A"
elif score >= 80: return "B"
elif score >= 70: return "C"
elif score >= 60: return "D"
else: return "F"
# Usage
numeric_calc = NumericGradeCalculator()
letter_calc = LetterGradeCalculator()
print(numeric_calc.describe_grade(numeric_calc.calculate_grade(85.5)))
print(letter_calc.describe_grade(letter_calc.calculate_grade(85)))
```
## Decorator Approach
This method uses a class decorator to wrap functions with additional functionality:
```python
from typing import Callable, TypeVar, Generic
T = TypeVar('T')
U = TypeVar('U')
class GradeCalculator(Generic[T, U]):
def __init__(self, calculate_grade: Callable[[T], U]):
self.calculate_grade = calculate_grade
def describe_grade(self, grade: U) -> str:
return f"The calculated grade is: {grade}"
@GradeCalculator
def numeric_grade_calculator(score: float) -> float:
return round(score / 10, 1)
@GradeCalculator
def letter_grade_calculator(score: int) -> str:
if score >= 90: return "A"
elif score >= 80: return "B"
elif score >= 70: return "C"
elif score >= 60: return "D"
else: return "F"
# Usage
print(numeric_grade_calculator.describe_grade(numeric_grade_calculator.calculate_grade(85.5)))
print(letter_grade_calculator.describe_grade(letter_grade_calculator.calculate_grade(85)))
```
## Comparison
Both approaches have their merits:
1. **Inheritance**:
- Suitable for complex scenarios with multiple methods
- Allows for extensive hierarchies and method overriding
- Explicit about types and relationships
2. **Decorators**:
- Provide automatic type inference
- Result in cleaner, more concise code for simple cases
- Easy to apply to existing functions
## Conclusion
For simple cases, decorators offer cleaner code through automatic type inference. For complex scenarios, traditional class-based approaches may be more suitable. Choose the method that best fits your specific use case and complexity requirements.