Python Union Type Hints: A Detailed Tutorial
Union type hints in Python allow you to specify that a variable, parameter, or return value can be of multiple types.
This feature enhances code readability and helps catch type-related bugs early in development.
Declare Union types
To declare a simple union type for a variable that can be either an integer or a float:
from typing import Union temperature: Union[float, int] = 23.5 temperature = 24 # This is also valid print(f"Current temperature: {temperature}")
Output:
Current temperature: 24
The variable temperature can accept both float and integer values.
You can use Union with multiple types in a function that processes customer data:
from typing import Union, List, Dict def process_customer_data(data: Union[Dict[str, str], List[str]]) -> str: if isinstance(data, dict): return f"Customer: {data['name']}, Age: {data['age']}" return f"Customer info: {', '.join(data)}" customer_dict = {"name": "Amr", "age": "30"} customer_list = ["Amr", "30", "Cairo"] print(process_customer_data(customer_dict)) print(process_customer_data(customer_list))
Output:
Customer: Amr, Age: 30 Customer info: Amr, 30, Cairo
The function accepts either a dictionary or a list as input and processes the data accordingly.
Using the pipe (|) operator
In Python 3.10+, you can use the more concise pipe operator for union types:
def calculate_price(quantity: int | float, price: int | float) -> float: return quantity * price print(calculate_price(2.5, 10)) print(calculate_price(3, 12.5))
Output:
25.0 37.5
The pipe operator provides a cleaner syntax while maintaining the same functionality as the Union type.
Type Checking with Union Types
You can use Mypy to check types in a function that processes payment amounts:
from typing import Union def process_payment(amount: Union[int, float, str]) -> float: if isinstance(amount, str): return float(amount.replace('$', '')) return float(amount) payment1 = process_payment("$99.99") payment2 = process_payment(100) print(f"Processed payments: ${payment1}, ${payment2}")
Output:
Processed payments: $99.99, $100.0
Type checkers verify that all operations are valid for each possible type in the Union.
Union Types in Function Signatures
Here’s a function that handles different types of user IDs:
from typing import Union def get_user(user_id: Union[int, str]) -> dict: users = { 1: {"name": "Heba", "age": 28}, "A123": {"name": "Karim", "age": 35} } return users[user_id] print(get_user(1)) print(get_user("A123"))
Output:
{'name': 'Heba', 'age': 28} {'name': 'Karim', 'age': 35}
The function accepts both integer and string IDs.
Union Types with Generic Types
Combine Union with other complex types
Here’s an example using Union with List and Dict for processing customer orders:
from typing import Union, List, Dict OrderData = Union[List[Dict[str, str]], Dict[str, List[str]]] def process_order(order: OrderData) -> str: if isinstance(order, list): return f"Multiple orders: {len(order)} items" return f"Single order with {len(order['items'])} items" order1 = [{"product": "Laptop", "price": "1200"}, {"product": "Mouse", "price": "25"}] order2 = {"customer": "Aisha", "items": ["Laptop", "Mouse"]} print(process_order(order1)) print(process_order(order2))
Output:
Multiple orders: 2 items Single order with 2 items
The OrderData type alias allows for flexible order data structures.
Nested Union types
Here’s how to handle nested product data with different types:
from typing import Union, Dict, List ProductPrice = Union[int, float, str] ProductData = Dict[str, Union[str, ProductPrice, List[str]]] def format_product(product: ProductData) -> str: price = product['price'] if isinstance(price, str): price = float(price.replace('$', '')) features = ', '.join(product.get('features', [])) return f"Product: {product['name']}, Price: ${price}, Features: {features}" product = { 'name': 'Smart Watch', 'price': '$199.99', 'features': ['Heart Rate Monitor', 'GPS', 'Water Resistant'] } print(format_product(product))
Output:
Product: Smart Watch, Price: $199.99, Features: Heart Rate Monitor, GPS, Water Resistant
Conditional Union types
Here’s how to use TypeGuard for runtime type checking:
from typing import TypeGuard, Union def is_positive_number(value: Union[int, float]) -> TypeGuard[float]: return isinstance(value, (int, float)) and value > 0 def process_value(value: Union[int, float]) -> str: if is_positive_number(value): return f"Valid positive number: {float(value)}" return "Invalid value" print(process_value(42)) print(process_value(-10))
Output:
Valid positive number: 42.0 Invalid value
TypeGuard helps narrow down the type of a value at runtime.
When to use Union types?
Use Union types when you need to handle multiple valid types for a single variable or parameter.
For example, a function that can process both JSON strings and dictionaries:
from typing import Union import json def process_config(config: Union[str, dict]) -> dict: if isinstance(config, str): return json.loads(config) return config json_config = '{"debug": true, "port": 8080}' dict_config = {"debug": False, "port": 9090} print(process_config(json_config)) print(process_config(dict_config))
Output:
{'debug': True, 'port': 8080} {'debug': False, 'port': 9090}
The function handles both string and dictionary inputs.
Avoid circular imports with type hints
To avoid circular imports, use string literals for forward references:
from typing import Union class Node: def __init__(self, value: int, next_node: Union[None, 'Node'] = None): self.value = value self.next_node = next_node node1 = Node(1) node2 = Node(2, node1) print(f"Node value: {node2.value}, Next node value: {node2.next_node.value}")
Output:
Node value: 2, Next node value: 1
String literals in type hints prevent circular import issues.
Mokhtar is the founder of LikeGeeks.com. He is a seasoned technologist and accomplished author, with expertise in Linux system administration and Python development. Since 2010, Mokhtar has built an impressive career, transitioning from system administration to Python development in 2015. His work spans large corporations to freelance clients around the globe. Alongside his technical work, Mokhtar has authored some insightful books in his field. Known for his innovative solutions, meticulous attention to detail, and high-quality work, Mokhtar continually seeks new challenges within the dynamic field of technology.