Mypy Mastery: Typing Functions That Work with TypedDict Children
Problem: You're working with a TypedDict in Python and need to define a function that operates on each of its values. However, Mypy, your trusty type checker, throws errors because it can't understand how your function interacts with the specific types of the TypedDict's children.
Rephrased: Imagine you have a box filled with different objects: apples, oranges, and pears. You want to write a function that can wash all the fruits in the box. Mypy, the inspector, wants to know exactly what type of fruit you'll be washing in each case.
Scenario:
Let's say you have a TypedDict representing a customer's order:
from typing import TypedDict
class Order(TypedDict):
item: str
quantity: int
price: float
order: Order = {
"item": "Apple",
"quantity": 5,
"price": 1.25
}
You want to create a function apply_discount
that applies a percentage discount to the price of each item in the order.
def apply_discount(order: Order, discount: float) -> None:
for key, value in order.items():
if key == "price":
order[key] = value * (1 - discount)
Mypy will complain here because it doesn't know that order[key]
will always be a float
when key
is "price."
Solution:
To solve this, we can use the typing.Any
type or generic types. However, both solutions introduce type ambiguity and limit Mypy's effectiveness.
The Best Approach: Use TypeVar and Generic Types
-
Define a generic type:
from typing import Generic, TypeVar, TypedDict T = TypeVar('T') class Order(TypedDict): item: str quantity: int price: float
-
Create a generic function:
from typing import Generic, TypeVar, TypedDict T = TypeVar('T') class Order(TypedDict): item: str quantity: int price: float def apply_discount(order: Order, discount: float) -> None: for key, value in order.items(): if isinstance(value, float): order[key] = value * (1 - discount)
Explanation:
TypeVar('T')
: This creates a type variableT
representing any type.Generic[T]
: We make our functionapply_discount
a generic function, allowing it to work with any type.isinstance(value, float)
: This check ensures we only apply the discount to values that are floats (in our case, the "price" value).
**Mypy will now be happy because it knows the function operates on a specific type. This approach allows for more flexibility and type safety without sacrificing Mypy's benefits. **
Additional Tips:
-
Use
typing.Union
to handle situations where a key could have multiple types:class Order(TypedDict): item: str quantity: int price: Union[float, int]
-
For more complex operations, consider creating specialized functions for each TypedDict field. This promotes code organization and clarity.
Resources:
- Mypy Documentation: https://mypy.readthedocs.io/en/stable/
- Python TypedDict Documentation: https://docs.python.org/3/library/typing.html#typing.TypedDict
By leveraging generic types and TypeVar
, you can write type-safe functions that work seamlessly with TypedDict children, ensuring your code is both readable and reliable.