Type Hints
Union Types
APIs often return different types depending on the situation - a lookup might return a user object or an error code. Union types let you express "this value can be one of several types" in a type-safe way, and type checkers help you handle each case correctly.
Unions in Collections
union_basics.py
# Union basics
from typing import Union
# Two equivalent forms
Value1 = Union[int, str]
Value2 = int | str
v1: Value1 =
v2: Value2 = "ten"
print("v1:", v1)
print("v2:", v2)
# Function parameter union
def stringify(x: int | str) -> str:
return str(x)
print("stringify(123) =", stringify(123))
print("stringify('abc') =", stringify("abc"))
# Union basics
from typing import Union
# Two equivalent forms
Value1 = Union[int, str]
Value2 = int | str
v1: Value1 =
v2: Value2 = "ten"
print("v1:", v1)
print("v2:", v2)
# Function parameter union
def stringify(x: int | str) -> str:
return str(x)
print("stringify(123) =", stringify(123))
print("stringify('abc') =", stringify("abc"))
# Union basics
from typing import Union
# Two equivalent forms
Value1 = Union[int, str]
Value2 = int | str
v1: Value1 =
v2: Value2 = "ten"
print("v1:", v1)
print("v2:", v2)
# Function parameter union
def stringify(x: int | str) -> str:
return str(x)
print("stringify(123) =", stringify(123))
print("stringify('abc') =", stringify("abc"))
isinstance_narrow.py
# Narrow a union with isinstance
def double(x: int | str) -> int | str:
# Narrow to int
if isinstance(x, int):
return x * 2
# Narrow to str
return x + x
print("double(10) =", double(10))
print("double('hi') =", double("hi"))
match_case.py
# match/case with unions
def describe(x: int | str | None) -> str:
match x:
case None:
return "none"
case int() as n:
return f"int:{n}"
case str() as s:
return f"str:{s}"
# unreachable
return "unknown"
print(describe(None))
print(describe(7))
print(describe("ok"))
tagged_dict.py
# Tagged dict payloads
from typing import Any
# A simple tagged payload pattern
# In real code you might use TypedDict, dataclasses, or pydantic.
def handle_event(evt: dict[str, Any]) -> str:
kind = evt.get("type")
if kind == "login":
user = evt.get("user")
return f"login:{user}" if isinstance(user, str) else "login:<?>"
if kind == "purchase":
amount = evt.get("amount")
return f"purchase:{amount}" if isinstance(amount, int) else "purchase:<?>"
return "unknown"
print(handle_event({"type": "login", "user": "Alice"}))
print(handle_event({"type": "purchase", "amount": 10}))
print(handle_event({"type": "purchase", "amount": "10"}))
union_collections.py
# Unions inside collections
# list[int|str]
items: list[int | str] = [1, "two", 3, "four"]
# Sum ints, collect strings
numbers: list[int] = [x for x in items if isinstance(x, int)]
strings: list[str] = [x for x in items if isinstance(x, str)]
print("numbers:", numbers)
print("strings:", strings)
# dict values union
settings: dict[str, int | str] = {"timeout": 10, "mode": "fast"}
print("settings:", settings)
Use unions when it genuinely improves the API and reflects reality; avoid unions that get too wide.
union type - a type that accepts values of multiple specified types, written as `Type1 | Type2`
type narrowing - using runtime checks like `isinstance()` to tell the type checker which specific type is in use
Exercise: practical.py
Build a response parser that handles success and error cases