Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extracting Original Arguments of Union #103762

Closed
hmc-cs-mdrissi opened this issue Apr 24, 2023 · 7 comments
Closed

Extracting Original Arguments of Union #103762

hmc-cs-mdrissi opened this issue Apr 24, 2023 · 7 comments
Labels
topic-typing type-feature A feature request or enhancement

Comments

@hmc-cs-mdrissi
Copy link
Contributor

hmc-cs-mdrissi commented Apr 24, 2023

Feature or enhancement

I'd like some way to runtime inspect a Union and be able to identify that,

A = Union[int, str]
B = Union[A, list[int]]

that A is first argument of B. Today get_args(B) == (int, str, list[int]) and even `B.args == (int, str, list[int])

Pitch

At runtime A is commonly a type alias and may be some long complex type. Being able to extract A back out can be helpful if you have dictionary of types for use cases similar to cattrs that define serializers/deserializers based on types. Or being able to generate documentation that uses A to simplify where it's possible documentation generator registers type aliases and stores some mapping of types -> short names.

How precisely this is done I'm more neutral on. It's fine if eq stays same and collapses them for comparison and get_args should also probably stay same behavior. Could __args__ change to preserve nesting so that B.__args__ == (A, list[int]) while get_args then handles collapsing? Alternative __original_args__ being added could work.

Previous discussion

This is kind of similar to Union ordering runtime question especially in motivation. There is a difference that even today there's already collapsing of Union types being done.

edit: One awkward point I just realized with this is it's unclear to me how nesting would work/mean anything with | syntax for defining Unions. I'm still mostly using 3.9 (one dependency I want still hasn't released 3.10+ support) so stuck with typing.Union for runtime usages but with | syntax nesting sorta disappears naturally. If this feature only makes sense for typing.Union and most newer usages are expected to move to | then it may make sense to just accept that distinguishing union alias inside a union is messy.

@hmc-cs-mdrissi hmc-cs-mdrissi added the type-feature A feature request or enhancement label Apr 24, 2023
@hmc-cs-mdrissi
Copy link
Contributor Author

I'm leaning towards closing this mostly due to this request making sense for typing.Union, but not as much for | syntax and adding functionality that only works with deprecated Union syntax probably doesn't make sense. I'll leave open for day/two if there's any ideas but fine if a maintainer wants to just close.

Am curious does pydantic (@samuelcolvin ) care about nesting/recognizing aliases of Unions?

@adriangb
Copy link
Contributor

I don't think Pydantic has an immediate use case for this, but I can see how it would be useful. Maybe we ran into this and just gave up in the past.

@hmc-cs-mdrissi I'm curious why you don't think this could work with | unions? The __orig_args__ proposal sounds reasonable to me.

@hmc-cs-mdrissi
Copy link
Contributor Author

hmc-cs-mdrissi commented Apr 24, 2023

A = int | str # Or some more complex union alias
B = A | list[int]

vs

B = int | str | list[int]

How would you distinguish those two with | syntax at runtime? In typing.Union case they would be distinguish as Union[Union[int, str], list[int]] vs Union[int, str, list[int]] but there's no way to use | with 3+ arguments.

@JelleZijlstra
Copy link
Member

Yes, I'm not sure this is something we could reasonably do. For the | operator, distinguishing between A = int | str; B = A | bool and A = int | str | bool would break referential transparency and it's not even clear how to implement it. For typing.Union we could do it, but that's deprecated and I wouldn't want to give it new features that the builtin | operator can't replicate.

Note that the new 3.12 syntax would allow this:

type A = int | str
type B = A | int
print(type(A))  # TypeAlias

@hmc-cs-mdrissi
Copy link
Contributor Author

hmc-cs-mdrissi commented Apr 24, 2023

For 3.12 do you mean print(get_args(B)[0]) would be A instead of int? That would be reasonable solution to me for 3.12 that for aliases where preserving name/type matters in Union use new type syntax.

@JelleZijlstra
Copy link
Member

Yes.

@hmc-cs-mdrissi
Copy link
Contributor Author

I'll close as I don't think the request as is makes sense for |. I think after PEP 695 is merged a test case to check new type alias provides way to preserve nesting would be good and can help add that case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-typing type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

4 participants