Skip to content

Merge typing.Union and types.UnionType #105499

Description

@JelleZijlstra

Currently, unions created through typing.Union[A, B] and through the PEP-604 syntax A | B are at runtime instances of completely different types, and they differ in exactly what elements they accept. This is confusing and makes it harder for users to detect unions at runtime.

I propose to proceed in two steps:

  1. Make typing.Union an alias for types.UnionType and make it so types.UnionType[A, B] works, accepting the same types Union accepts now.
  2. Loosen the rules for what the | operator accepts to accept more types that are commonly used in unions.

Linked PRs

Activity

  1. self-assigned this
    on Jun 8, 2023
  2. JelleZijlstra commented on Jun 8, 2023

    @JelleZijlstra
    MemberAuthor

    I put up a first implementation at #105511. Some thoughts on the details:

    • I implemented it by making typing.Union just a re-export of types.UnionType, but I feel it might actually be more intuitive if typing.Union was the canonical name of the object, and types.UnionType was an alias. We could change the implementation to set the module and name differently.
    • The repr() of all unions changes to use the | syntax.
    • I added dummy __name__, __qualname__, and __origin__ fields to types.UnionType to satisfy some test_typing tests.
    • We no longer support writing to a union's __args__ attribute. A test relied on this, but nobody should have been assigning to __args__ anyway, so I'm fine with this change.
    • There's a couple of behavior changes around issubclass() and subclassing because types.UnionType is (unlike typing.Union) an actual type.
  3. tibbe commented on Jun 14, 2023

    @tibbe

    This broken an AST walker I had written for Python 3.9, after I upgraded all my Union[X, Y] to X | Y, something is expected to be purely syntactical change.

  4. samsonvd commented on Sep 28, 2024

    @samsonvd

    Will this also fix the issue of unsupported operand type(s) for |: 'str' and ... when using a string reference of a type?

    i.e.

    class Foo:
        def get_self(self) -> "Foo" | None:
            pass
    
    # Traceback (most recent call last):
    #   File "<stdin>", line 1, in <module>
    #   File "<stdin>", line 2, in Foo
    # TypeError: unsupported operand type(s) for |: 'str' and 'NoneType'
    
    from typing import Union
    class Foo:
        def get_self(self) -> Union["Foo", None]:
            pass
    
    # no issues
  5. JelleZijlstra commented on Sep 28, 2024

    @JelleZijlstra
    MemberAuthor

    @marscapone, no, but other changes in Python 3.14 (PEP-649) will fix that case.

  6. hauntsaninja commented on Sep 28, 2024

    @hauntsaninja
    Contributor

    See also https://mypy.readthedocs.io/en/stable/runtime_troubles.html and from __future__ import annotations, for mostly solutions in Python 3.7 onwards

  7. added a commit that references this issue on Mar 4, 2025
  8. added 3 commits that reference this issue on Mar 31, 2025
  9. serhiy-storchaka commented on Jul 24, 2025

    @serhiy-storchaka
    Member

    I think this was a mistake. types.UnionType was intentionally named so to avoid confusion with typing.Union (see #88895
    ). types.UnionType should not be subscriptable because it is not generic type (and if it was a generic type, subscription would have different semantic than for typing.Union). types.UnionType corresponds to typing._UnionGenericAlias, not typing.Union.

  10. added 2 commits that reference this issue on Jul 24, 2025
  11. added 2 commits that reference this issue on Sep 16, 2025
  12. added a commit that references this issue on Aug 7, 2026
  13. added a commit that references this issue on Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions