union
1. Basic Understanding of Union Types
Union types (written as Union[X, Y] or using the | operator in Python 3.10+) allow you to specify that a value could be one of several types. This is particularly useful when a function can accept or return multiple types of values.
2. Syntax and Evolution
# Python 3.7+ (using typing module)
from typing import Union
# Traditional syntax
x: Union[int, str] = "hello"
# Python 3.10+ (using | operator)
y: int | str = 42
3. Visual Representation of Union Types
Let me create a diagram to show how Union types work conceptually:
flowchart TD
V[Variable with Union Type] --> T{Type Check}
T -->|is int| I[Integer Processing]
T -->|is str| S[String Processing]
I --> R[Result]
S --> R
style V fill:#f9f,stroke:#333,stroke-width:2px
style T fill:#bbf,stroke:#333,stroke-width:2px
style I fill:#bfb,stroke:#333,stroke-width:2px
style S fill:#bfb,stroke:#333,stroke-width:2px
style R fill:#fbb,stroke:#333,stroke-width:2px
4. Practical Examples
from typing import Union
# Function accepting either int or float
def double_number(value: Union[int, float]) -> Union[int, float]:
return value * 2
# Function accepting string or list of strings
def process_data(data: Union[str, list[str]]) -> str:
if isinstance(data, list):
return ", ".join(data)
return data.upper()
# Using the functions
print(double_number(10)) # 20
print(double_number(3.14)) # 6.28
print(process_data("hello")) # HELLO
print(process_data(["a", "b"])) # a, b
5. Advanced Usage and Patterns
from typing import Union, Optional, List, Dict
# Optional is a shorthand for Union[X, None]
def greet(name: Optional[str] = None) -> str:
return f"Hello, {name or 'World'}!"
# Nested Unions
JsonValue = Union[
str,
int,
float,
bool,
None,
List['JsonValue'],
Dict[str, 'JsonValue']
]
def process_json(data: JsonValue) -> str:
return str(data)
6. Common Use Cases
- Error Handling
def divide(a: float, b: float) -> Union[float, str]:
try:
return a / b
except ZeroDivisionError:
return "Division by zero is not allowed"
- Optional Parameters
def create_user(
name: str,
age: Optional[int] = None,
email: Optional[str] = None
) -> dict:
user = {"name": name}
if age is not None:
user["age"] = age
if email is not None:
user["email"] = email
return user
7. Best Practices and Tips
- Type Narrowing
def process_value(value: Union[int, str]) -> str:
# Type narrowing using isinstance
if isinstance(value, int):
return str(value * 2) # Type checker knows value is int here
return value.upper() # Type checker knows value is str here
- Using TypeAlias for Complex Unions
from typing import TypeAlias
# Create a type alias for complex union types
NumberType: TypeAlias = Union[int, float, complex]
StringList: TypeAlias = Union[str, list[str]]
8. Common Pitfalls to Avoid
- Unnecessary Unions
# Bad: Unnecessary Union
def process_data(value: Union[str, str]) -> str:
return value.upper()
# Good: Simple type hint
def process_data(value: str) -> str:
return value.upper()
- Overuse of Union Types
# Bad: Too many types can make code hard to maintain
def complex_function(
data: Union[str, int, float, list, dict, tuple]
) -> Union[str, int, float, list, dict, tuple]:
return data
# Good: Consider creating a custom type or narrowing the scope
from typing import Any
def complex_function(data: Any) -> Any:
return data
Key Takeaways
- Union types provide flexibility in type hints while maintaining type safety
- They're particularly useful for functions that can handle multiple types
- Type narrowing is important when working with Union types
- Python 3.10+ offers a more concise syntax with the
|operator - Optional is a special case of Union with None