Skip to content

gh-105499: Merge typing.Union and types.UnionType - #105511

Merged
JelleZijlstra merged 52 commits into
python:mainfrom
JelleZijlstra:unifyunion
Mar 4, 2025
Merged

gh-105499: Merge typing.Union and types.UnionType#105511
JelleZijlstra merged 52 commits into
python:mainfrom
JelleZijlstra:unifyunion

Conversation

@JelleZijlstra

@JelleZijlstraJelleZijlstra commented Jun 8, 2023

Copy link
Copy Markdown
Member

Comment threadLib/test/test_typing.py Outdated
@AlexWaygood

Copy link
Copy Markdown
Member

Would we be able to get rid of typing._make_union, and just have TypeVar etc. call UnionType.__class_getitem__ directly?

@JelleZijlstra

Copy link
Copy Markdown
MemberAuthor

Would we be able to get rid of typing._make_union, and just have TypeVar etc. call UnionType.__class_getitem__ directly?

Yes

@AlexWaygood

Copy link
Copy Markdown
Member

Instead of deleting typing.Union completely, adding UnionType.__class_getitem__, and making typing.Union an alias for types.UnionType, I wonder if we could just change typing.Union so that it's a special form that just returns instances of UnionType:

fromoperatorimportor_fromfunctoolsimportreduce@_SpecialFormdefUnion(self, parameters):
returnreduce(or_, parameters)

That would avoid the issue of "Should we rename types.UnionType to be Union", as we'd keep them as distinct objects.

But I know your plan is to deal with handling forward refs in | expressions later, which would mean that^ idea above wouldn't work right now (unions involving forward references would break).

So perhaps you could instead expose a _secret_undocumented_constructor method for types.UnionType (we can bikeshed over the name), and then just do

@_SpecialFormdefUnion(self, parameters):
returntypes.UnionType._secret_undocumented_constructor(parameters)

Or we could just expose the constructor of types.UnionType, of course.

@AlexWaygood

AlexWaygood commented Jun 8, 2023

Copy link
Copy Markdown
Member

Also you can now get rid of this function as part of this PR; prior callers of the function can now just use isinstance() checks against types.UnionType:

cpython/Lib/functools.py

Lines 842 to 844 in 6a8b862

def_is_union_type(cls):
fromtypingimportget_origin, Union
returnget_origin(cls) in {Union, types.UnionType}

@JelleZijlstra

Copy link
Copy Markdown
MemberAuthor

That would avoid the issue of "Should we rename types.UnionType to be Union", as we'd keep them as distinct objects.

I'd rather not keep them as distinct objects, though; that means we still have two objects that to a user look like the same thing. Plus, we'd have get_origin(Union[int, str]) != Union.

@AlexWaygood

AlexWaygood commented Jun 8, 2023

Copy link
Copy Markdown
Member

I guess one of my reservations here is that UnionType ends up looking like a pretty weird type. You can't construct instances of it directly:

>>> from types import UnionType
>>> UnionType(int, str)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot create 'types.UnionType' instances

...Except wait, you can, we now have a "secret __class_getitem__ backdoor" to create instances directly:

>>> UnionType[int, str, bytes]
int | str | bytes

It's very unusual for __class_getitem__ to just directly return instances of the class, and it feels really weird to make that the only way you're allowed to construct instances of the class.

Maybe that means we should just expose the constructor as well as adding __class_getitem__...?

Plus, we'd have get_origin(Union[int, str]) != Union.

Good point, that would indeed be confusing.

@JelleZijlstra

Copy link
Copy Markdown
MemberAuthor

I can make the constructor work. Should it take *args and union them all together?

@AlexWaygood

Copy link
Copy Markdown
Member

I can make the constructor work. Should it take *args and union them all together?

That makes sense to me!

@AlexWaygoodAlexWaygood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, in my opinion. From a design perspective, the only weirdnesses I can see are that both of these become valid at runtime:

fromtypesimportUnionTypefromtypingimportUnionUnionType[int, str]
Union(int, str)

I can live with that, though, and hopefully linters can flag those uses. (Type checkers almost certainly will, anyway.) On our side, we can just not document that you can do either of those things.

x < y
# Check that we don't crash if typing.Union does not have a tuple in __args__
y = typing.Union[str, int]
y.__args__ = [str, int]

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.__args__ is no longer writable.

Comment threadLib/test/test_types.py Outdated

bt = BadType('bt', (), {})
bt2 = BadType('bt2', (), {})
# Comparison should fail and errors should propagate out for bad types.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new code there are fewer code paths that trigger the equality comparison.

@JelleZijlstra

Copy link
Copy Markdown
MemberAuthor

It's been long enough, I'm planning to merge this once the tests pass again.

@JelleZijlstraJelleZijlstra added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Mar 3, 2025
@bedevere-bot

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @JelleZijlstra for commit cab69f0 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F105511%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

@hugovk

Copy link
Copy Markdown
Member

This PR causes a potential regression: #131933.

JelleZijlstra added a commit to JelleZijlstra/cpython that referenced this pull request Mar 31, 2025
Leftover from python#105511 I believe. GitHub code search found no usages other
than copies of typing.py and lists of stdlib functions.
JelleZijlstra added a commit that referenced this pull request Mar 31, 2025
Leftover from #105511 I believe. GitHub code search found no usages other
than copies of typing.py and lists of stdlib functions.
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Sep 16, 2025
…s.UnionType (python#105511)"
This reverts commit d1db43c.
This reverts commit 0f511d8.
This reverts commit dc6d66f.
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Sep 16, 2025
…s.UnionType (python#105511)"
This reverts commit d1db43c.
This reverts commit 0f511d8.
This reverts commit dc6d66f.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants

@JelleZijlstra@AlexWaygood@carljm@bedevere-bot@hugovk@gvanrossum@Fidget-Spinner@Gobot1234@kumaraditya303