Python is a dynamically typed language, and you can run programs without explicitly specifying variable types. However, in large projects or team development, it is necessary to make type information explicit to improve code readability and maintainability. The feature introduced to address this need is “type hints”. Type hints are a feature that specifies the expected data types for variables and function arguments and return values. This makes it easier for other developers reading the code, or your future self, to understand the intent of variables. Additionally, using static analysis tools allows you to detect type errors before runtime.
The Importance of Union Types
Union types are a kind of type hint that allow multiple different data types. For example, if a function needs to accept either an integer or a string as input, using a union type lets you reflect that flexibility in the code. Consider the following example.
from typing import Union
def process_data(data: Union[int, str]) -> None:
print(data)
This function accepts an integer or a string and prints it as is. With type hints, the data types the function can accept become clear, allowing other developers to use it confidently.
Uses of Union Types
When creating functions that handle different data formats.
When an API response may have multiple possible types.
When designing libraries or utility functions that need to support flexible input formats.
In the next chapter, we will delve deeper into the basic usage of union types.
2. What is a Union type?
Definition and Basic Concepts of Union Types
Union types are a feature in Python used to specify multiple different data types as a single type hint. This allows code to maintain both flexibility and type safety even when arguments or return values can be of multiple types. Union types are primarily used for the following purposes.
Defining functions that accept different data formats.
Developing libraries or tools that handle flexible data types.
Strengthening pre‑validation with type‑checking tools.
Examples of Union Types
For example, consider a case where you define a function that accepts both strings and numbers.
from typing import Union
def add_values(value1: Union[int, float], value2: Union[int, float]) -> Union[int, float]:
return value1 + value2
In this example, the function add_values accepts integers and floating‑point numbers and returns the same type as its output. By using Union types, you can write concise code that handles multiple data types.
Features of Union Types
Flexibility Allowing multiple types lets you implement functions that handle various input data in a simple way.
Enhanced Type Checking Using static analysis tools (e.g., mypy) enables you to detect errors early through type checking.
Improved Readability Clear type information for functions and variables makes the code easier for developers to understand.
Concrete Example Where Union Types Are Useful
The following code is an example of a function that accepts either a list or a single string.
from typing import Union, List
def format_data(data: Union[str, List[str]]) -> List[str]:
if isinstance(data, list):
return data
else:
return [data]
This function accepts either a single string or a list of strings and returns them in list form. In this way, Union types help ensure input data flexibility while maintaining type consistency.
Bridge to the Next Chapter
The next section will explain in detail the way Union types were specified in Python versions prior to 3.10 and highlight the differences with the latest version.
3. Traditional Union Type Specification
Union Type Specification Prior to Python 3.10
In versions prior to Python 3.10, you had to import Union from the typing module to specify a union type. This approach is still available, but Python 3.10 and later introduced a more concise syntax.
Basic Usage
The following code demonstrates how to use union types in Python versions prior to 3.10.
from typing import Union
def process_input(data: Union[int, float]) -> float:
return float(data)
In this example, the argument data can be either an integer or a floating-point number, and the return value is a float. Union[int, float] ensures flexibility by allowing multiple types.
Various Uses of Union Types
Specifying Types for Multiple Arguments
An example of a function that accepts arguments of different types.
from typing import Union
def display_value(value: Union[int, str]) -> None:
print(f"Value: {value}")
When Making Return Types Flexible
An example where a function’s return value can be of multiple types.
from typing import Union
def process_data(data: str) -> Union[int, None]:
if data.isdigit():
return int(data)
return None
Code Readability and Issues
The traditional Union is very powerful, but it had the following issues.
Code tends to become verbose.
Readability decreases when specifying long type lists.
Typos can easily cause type errors.
Code Example: Complex Type Annotations
from typing import Union, List, Tuple
def process_items(items: Union[List[int], Tuple[int, ...]]) -> None:
for item in items:
print(item)
This code accepts a list or tuple of integers, but the repeated use of Union makes the code longer.
Transition to the Next Chapter
In Python 3.10, the union type syntax was simplified to address these issues. The next section details the new syntax introduced in Python 3.10 and later.
4. New Union Type Specification (PEP 604 Support)
What is the new syntax introduced in Python 3.10?
In Python 3.10, a new syntax was introduced that allows union types to be written more simply. This change was proposed in PEP 604 (Python Enhancement Proposal) and greatly improves code readability and writing efficiency.
In the new syntax, you can specify multiple types using the pipe operator | instead of the traditional Union.
Basic Example of the New Syntax
The following code shows an example of specifying a union type using the new syntax.
def process_input(data: int | float) -> float:
return float(data)
In this example, the function process_input accepts an integer or a floating-point number and returns a floating-point number. It has the same meaning as the traditional Union[int, float], but the notation is shorter and more readable.
Comparison of Old and New Syntax
Python 3.10 and earlier (old syntax)
Python 3.10 and later (new syntax)
Union[int, str]
int | str
Union[List[int], Tuple[int, ...]]
List[int] | Tuple[int, ...]
Optional[str] (type that includes None)
str | None
Benefits of the New Syntax
Simplified Code
The new syntax uses fewer characters, making type hint specifications more intuitive and easier to understand.
Improved Readability
The pipe operator | logically means “or,” so the purpose of a union type is immediately clear.
Easy Combination with Variable-Length Tuples and Lists
Even nested complex types can be written simply.
Concrete Examples Using the New Syntax
Function Accepting Multiple Data Types
def display_value(value: int | str) -> None:
print(f"Value: {value}")
Function Returning Multiple Types
def process_data(data: str) -> int | None:
if data.isdigit():
return int(data)
return None
The new syntax can only be used in Python 3.10 and later, so you need to be careful about compatibility in older environments.
Compatibility with Static Analysis Tools
Static analysis tools such as mypy support the new syntax, but support may be limited in some versions. Check in advance.
Transition to the Next Chapter
In the next section, we will compare the old and new union type syntax with concrete code examples and explore real-world use cases. This will help you understand more practically how to apply them in development.
5. Practical Union Type Applications
Usage Example in Data Analysis Tools
Union types are commonly used in data analysis and statistical processing. Below is an example of a function that accepts an integer or a list as input data and processes it.
def calculate_average(data: int | list[int]) -> float:
if isinstance(data, int):
return float(data)
elif isinstance(data, list):
return sum(data) / len(data)
else:
raise TypeError("Unsupported data type")
Key Points
Ensuring Flexibility
Because this function can handle both a single integer a list, it relaxes restrictions on input formats.
Error Handling
It returns an error message for unexpected data types, making it easier to identify issues.
Example of API Response Processing
Responses from an API can come in multiple formats. Union types help handle these different formats.
from typing import Any
def parse_response(response: dict[str, Any] | list[dict[str, Any]]) -> list[dict[str, Any]]:
if isinstance(response, dict):
return [response] # Convert a single object to a list
elif isinstance(response, list):
return response # Return as is
else:
raise ValueError("Invalid response format")
Key Points
Supporting Flexible Input Formats
It can handle both a single object and a list of objects.
Ensuring Consistent Output
Since the output is always a list regardless of the input format, subsequent processing is simplified.
Example of User Input Form Validation
User input can take various forms, and union types allow flexible validation.
def validate_input(data: str | int | float) -> str:
if isinstance(data, str):
if not data.strip():
raise ValueError("String cannot be empty")
return data
elif isinstance(data, (int, float)):
if data < 0:
raise ValueError("Number cannot be negative")
return str(data)
else:
raise TypeError("Unsupported input type")
Example of Configuration Management Accepting Multiple Data Formats
When loading configuration files, the data format is often a string or a dictionary.
def load_config(config: str | dict[str, str]) -> dict[str, str]:
import json
if isinstance(config, str):
# If it's a file path, read it as JSON
with open(config, 'r') as file:
return json.load(file)
elif isinstance(config, dict):
# If it's already a dictionary, return it as is
return config
else:
raise TypeError("Invalid configuration format")
Conclusion
These practical examples show that union types help write flexible and safe code. They can be applied in a wide range of scenarios such as data analysis, API response processing, and user input validation.
6. Caveats and Best Practices
Using Type Aliases
Union types provide flexibility by combining multiple types, but if they become overly complex, code readability can suffer. Using “type aliases” is recommended as a solution.
To use union types safely and effectively, keep the following points in mind.
Use type aliases to improve readability and reusability.
Leverage static analysis tools to detect type errors early.
Avoid overcomplicating type annotations; aim for simple, easy-to-understand code.
Appropriately use default values and optional types to strengthen error handling.
7. Frequently Asked Questions (FAQ)
Q1. What’s the difference between Union types and Any types?
Union types and Any types both allow handling multiple types, but they differ significantly in purpose and usage.
Item
Union type
Any type
Type specification
Explicitly specify types (e.g., int | str)
Accept any type (e.g., Any)
Type checking
Error for types other than the specified ones
No type checking performed
Use cases
Limited combination of types
Generic functions or variables without type constraints
Code example
Union type:
def display_value(value: int | str) -> None:
print(value)
Any type:
from typing import Any
def display_value(value: Any) -> None:
print(value)
Q2. Is there compatibility between Union types before and after Python 3.10?
Yes, they are compatible. Python 3.10 introduced a new syntax, but the old Union syntax remains usable.
Example: Compatible code
from typing import Union
# Old syntax
def old_union(data: Union[int, str]) -> None:
print(data)
# New syntax
def new_union(data: int | str) -> None:
print(data)
Q3. Do type hints affect code execution speed?
No, type hints are not evaluated at runtime. They are a development aid and are ignored by the interpreter during execution. However, using static analysis tools (such as mypy) provides the following benefits.
Bug prevention through error detection while coding.
Improved development efficiency via enhanced IDE autocomplete.
Q4. What’s the difference between Optional types and Union types?
Optional types are a special kind of Union type that combine a type with None. Below are examples illustrating the difference. Union type example
def process_data(data: int | None) -> str:
return str(data) if data is not None else "No data"
Optional type example
from typing import Optional
def process_data(data: Optional[int]) -> str:
return str(data) if data is not None else "No data"
Q5. Are static analysis tools required?
They are not mandatory, but strongly recommended.
Reasons
Improve code quality.
Streamline debugging.
Maintain code consistency in team development.
Key tools
mypy: Ideal for strict type checking.
Pyright: Provides fast, real-time type checking.
Example usage of mypy
pip install mypy
mypy script.py
Summary
This FAQ covered common questions about Union types and explained their differences and appropriate usage in detail.
Since Union types and Any types serve different purposes, choose them according to your needs.
Both the new and old syntaxes are compatible, so you can use them confidently during transition periods.
Using static analysis tools together can enhance code quality and safety.
8. Recommended Tools & Libraries
mypy: Static Type Checking Tool
What is mypy? mypy parses Python type hints and performs static type checking. It verifies that type annotations, including union types, are correct, helping to catch bugs early.
Features
Detect type errors in advance.
Improve code quality in large projects.
Fully supports Python type hints.
Installation
pip install mypy
Example
from typing import Union
def process_input(data: Union[int, str]) -> str:
return str(data)
process_input(100) # OK
process_input("hello") # OK
process_input(10.5) # error detected
Run Type Check
mypy script.py
Pyright: Fast Type Checking Tool
What is Pyright? Pyright is a Python type checking tool developed by Microsoft, with seamless integration especially with VSCode. It performs real-time type checking and is handy when working with union types.
Features
Fast type checking.
Seamless integration with VSCode.
Enhanced type inference.
Installation
npm install -g pyright
Example
def validate_input(data: int | str) -> str:
if isinstance(data, int):
return str(data)
elif isinstance(data, str):
return data
return "Invalid input" # error detected
Pydantic: Data Validation and Type Checking
What is Pydantic? Pydantic is a library that uses type hints for data validation and serialization. It lets you manage complex data models with union types concisely.
Features
Powerful validation of JSON and API responses.
Supports automatic type conversion.
Enables definition of type-safe data models.
Installation
pip install pydantic
Example
from pydantic import BaseModel
from typing import Union
class Item(BaseModel):
name: str
value: Union[int, float]
item = Item(name="example", value=42)
print(item)
IDE Support: PyCharm and VSCode
Modern IDEs provide type hint completion error checking, streamlining development with union types.
Recommended IDEs
PyCharm: Powerful type hint completion and warning display. Packed with many features tailored for Python development.
VSCode: Lightweight and fast type checking via extensions (Python, Pyright).
Conclusion
By leveraging these tools and libraries, you can use union types more effectively, enhancing code safety and development efficiency. Recommended Tools
mypy: Ideal for strict type checking.
Pyright: Provides fast, real-time type checking.
Pydantic: Powerful for data validation and managing complex models.
PyCharm/VSCode: Code completion and error checking boost development efficiency.
9. Summary
Key Points of Union Type Convenience and Evolution
In this article, we explained Union types in Python in detail, from basic concepts to use cases and best practices. Below is a recap of the main points about Union types.
Basic Concepts
A Union type is a type hint feature that allows multiple types.
It provides flexibility while maintaining type safety.
Differences Between Old and New Syntax
Before Python 3.10, Union[X, Y] is used.
From Python 3.10 onward, the pipe operator X | Y can be used for concise notation.
Use Cases and Application Scenarios
Used in practical scenarios such as data analysis, API response handling, and input validation.
Use type aliases to improve readability when managing complex types.
Cautions and Best Practices
Combine static analysis tools (mypy, Pyright) to detect type errors early.
Avoid over-specifying types; aim for simple, easy-to-understand structures.
Recommended Tools & Libraries
Strengthen static analysis with mypy or Pyright.
Use Pydantic to streamline data validation and model management.
Proposing Improved Development Efficiency with Python 3.10+
With the new syntax introduced in Python 3.10, writing Union types has become dramatically more concise. This improves development efficiency and significantly enhances code readability and maintainability. Example Using the New Syntax:
def process_input(data: int | str) -> str:
return str(data)
Thus, you can write concise and intuitive code, enabling a more modern programming style.
Next Steps for Readers
Readers who have learned the basics and applications of Union types are encouraged to take the following actions to further hone their skills.
Work on Practical Exercises
Use Union types in real projects to try data validation and API handling.
Leverage Additional Learning Resources
Refer to the official Python documentation (python.org) to check the latest features.
Use the documentation for type-checking tools (mypy, Pyright) to learn configuration and advanced usage.
Apply to Projects
Introduce Union types into existing code to improve readability and safety.
Use version control tools (like Git) to record changes while gradually updating.
Conclusion
Union types are a powerful feature for enhancing flexibility and type safety in Python code. Especially with the new syntax in Python 3.10+, code becomes more concise, increasing its practicality. Use this guide as a reference to actively employ Union types and improve your practical programming skills. Keep striving to improve your Python skills!