如何为支持重叠Union类型的函数/方法参数添加类型标注?
First, define your core type aliases clearly:
import typing as tp from fractions import Fraction from decimal import Decimal # Define valid type groups Real = tp.Union[int, float, Fraction] DecLike = tp.Union[int, Decimal]
1. Generic Class with Constrained TypeVar
For class-based implementations, use a constrained TypeVar to enforce that instance methods only accept compatible types. This eliminates type checker errors for no-arg methods and ensures type consistency across operations.
# TypeVar constrained to either Real or DecLike groups T = tp.TypeVar("T", Real, DecLike) class Numeric(tp.Generic[T]): def __init__(self, value: T) -> None: self.value: T = value def add(self, other: T) -> "Numeric[T]": # Runtime validation matching your existing logic has_decimal = isinstance(self.value, Decimal) or isinstance(other, Decimal) if has_decimal: if not isinstance(self.value, (int, Decimal)) or not isinstance(other, (int, Decimal)): raise TypeError("Cannot mix Decimal with non-int/non-Decimal types") else: if not isinstance(self.value, (int, float, Fraction)) or not isinstance(other, (int, float, Fraction)): raise TypeError("Invalid type for Real number operation") # PyRight recognizes this operation is safe since T is a single valid group return Numeric(self.value + other) # No-arg method works seamlessly with the generic type def get_value(self) -> T: return self.value
2. TypeGuard for Standalone Functions
For standalone functions, use TypeGuard to narrow types after runtime checks. This tells PyRight exactly what type variables are after validation, eliminating false positive errors about unsupported operations.
# TypeGuard functions to validate membership in valid type groups def is_real(x: tp.Any) -> tp.TypeGuard[Real]: return isinstance(x, (int, float, Fraction)) def is_declike(x: tp.Any) -> tp.TypeGuard[DecLike]: return isinstance(x, (int, Decimal)) def add(a: tp.Union[Real, DecLike], b: tp.Union[Real, DecLike]) -> tp.Union[Real, DecLike]: # Check if we're dealing with Decimal-compatible types if isinstance(a, Decimal) or isinstance(b, Decimal): if not (is_declike(a) and is_declike(b)): raise TypeError("Cannot mix Decimal with non-int/non-Decimal types") return a + b # PyRight confirms a/b are DecLike here else: if not (is_real(a) and is_real(b)): raise TypeError("Invalid type for Real number operation") return a + b # PyRight confirms a/b are Real here
3. Function Overloads (Standalone Use)
If you prefer explicit signatures, define overloads for each valid type group. This avoids flattened union confusion and lets PyRight infer correct types at check time.
@tp.overload def add(a: Real, b: Real) -> Real: ... @tp.overload def add(a: DecLike, b: DecLike) -> DecLike: ... def add(a: tp.Union[Real, DecLike], b: tp.Union[Real, DecLike]) -> tp.Union[Real, DecLike]: # Same runtime validation as before has_decimal = isinstance(a, Decimal) or isinstance(b, Decimal) if has_decimal: if not isinstance(a, (int, Decimal)) or not isinstance(b, (int, Decimal)): raise TypeError("Cannot mix Decimal with non-int/non-Decimal types") else: if not isinstance(a, (int, float, Fraction)) or not isinstance(b, (int, float, Fraction)): raise TypeError("Invalid type for Real number operation") return a + b
Key Benefits
- Generic Class: The constrained TypeVar ensures all operations use types from a single valid group, so PyRight never encounters incompatible combinations.
- TypeGuard: Explicit type narrowing removes false positives by letting the checker know exactly what types are valid after runtime checks.
- Overloads: Clear signatures for valid type pairs enable PyRight to reject invalid combinations during static analysis.
Content of the question comes from Stack Exchange, asked by I Like Python

