Python Union Types: Guide to New & Old Syntax with Examples

目次

1. Introduction

What are Python type hints?

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

  1. When creating functions that handle different data formats.
  2. When an API response may have multiple possible types.
  3. 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

  1. Flexibility Allowing multiple types lets you implement functions that handle various input data in a simple way.
  2. Enhanced Type Checking Using static analysis tools (e.g., mypy) enables you to detect errors early through type checking.
  3. 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

  1. Simplified Code
  • The new syntax uses fewer characters, making type hint specifications more intuitive and easier to understand.
  1. Improved Readability
  • The pipe operator | logically means “or,” so the purpose of a union type is immediately clear.
  1. 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

Example of Complex Type Specification

def combine_data(data: list[int] | tuple[int, ...]) -> list[int]:
    return list(data)

Cautions and Compatibility of the New Syntax

  1. Not Available in Python 3.10 and Earlier
  • The new syntax can only be used in Python 3.10 and later, so you need to be careful about compatibility in older environments.
  1. 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

  1. Ensuring Flexibility
  • Because this function can handle both a single integer a list, it relaxes restrictions on input formats.
  1. 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

  1. Supporting Flexible Input Formats
  • It can handle both a single object and a list of objects.
  1. 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.

Example of a Type Alias

DataType = int | float | str

def process_data(data: DataType) -> str:
    return str(data)
In this example, creating an alias called DataType consolidates multiple type annotations. This makes the code simpler and improves reusability.

Leveraging Static Analysis Tools

When using union types, combining them with type-checking tools can further increase code reliability.

Basic Usage of mypy

pip install mypy
mypy script.py
Code example:
from typing import Union

def calculate_total(price: int | float, quantity: int) -> float:
    return price * quantity
Running mypy on this code validates type consistency in advance, helping prevent errors before they occur.

Risks of Overly Specific Type Annotations

Union types provide flexibility, but overusing them can increase code complexity and make it harder to understand.

Bad Example

def complex_function(data: int | float | str | list[str] | dict[str, int]) -> str:
    # Too complex to understand
    return str(data)

Improved Example

SimpleType = int | float | str
ComplexType = list[str] | dict[str, int]

def process_simple(data: SimpleType) -> str:
    return str(data)

def process_complex(data: ComplexType) -> str:
    return str(data)

Considering Type Safety and Default Values

When setting default values for functions, using Optional (or | None) combined with a union type and None can be helpful.

Function Handling Default Values

def fetch_data(key: str, default: str | None = None) -> str:
    data = {"name": "Python", "version": "3.10"}
    return data.get(key, default) or "Unknown"

Summary

To use union types safely and effectively, keep the following points in mind.
  1. Use type aliases to improve readability and reusability.
  2. Leverage static analysis tools to detect type errors early.
  3. Avoid overcomplicating type annotations; aim for simple, easy-to-understand code.
  4. 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.
ItemUnion typeAny type
Type specificationExplicitly specify types (e.g., int | str)Accept any type (e.g., Any)
Type checkingError for types other than the specified onesNo type checking performed
Use casesLimited combination of typesGeneric 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.
  1. Bug prevention through error detection while coding.
  2. 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

  1. Improve code quality.
  2. Streamline debugging.
  3. 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.
  1. Basic Concepts
  • A Union type is a type hint feature that allows multiple types.
  • It provides flexibility while maintaining type safety.
  1. 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.
  1. 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.
  1. 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.
  1. 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.
  1. Work on Practical Exercises
  • Use Union types in real projects to try data validation and API handling.
  1. 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.
  1. 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!
侍エンジニア塾