Skip to content

(🐞) isinstance works with typing.Unions on 3.10+ #7505

Closed as not planned
Closed as not planned

Description

@KotlinIsland
from typing import Union

print(isinstance(1, Union[str, int]))  # error: Argument 2 to "isinstance" has incompatible type "object"; expected "Union[type, UnionType, Tuple[Union[type, UnionType, Tuple[Any, ...]], ...]]"

But this outputs True

Union[X, Y] is stubbed to return object when it really returns a typing._UnionGenericAlias to typing.Union so that would need to be fixed first.

playground

Activity

  1. srittau commented on Mar 17, 2022

    @srittau
    Collaborator

    The stubs look correct to me:

    typeshed/stdlib/builtins.pyi

    Lines 1161 to 1172 in e8286e7

    # We need recursive types to express the type of the second argument to `isinstance` properly, hence the use of `Any`
    if sys.version_info >= (3, 10):
    def isinstance(
    __obj: object, __class_or_tuple: type | types.UnionType | tuple[type | types.UnionType | tuple[Any, ...], ...]
    ) -> bool: ...
    def issubclass(
    __cls: type, __class_or_tuple: type | types.UnionType | tuple[type | types.UnionType | tuple[Any, ...], ...]
    ) -> bool: ...
    else:
    def isinstance(__obj: object, __class_or_tuple: type | tuple[type | tuple[Any, ...], ...]) -> bool: ...
    def issubclass(__cls: type, __class_or_tuple: type | tuple[type | tuple[Any, ...], ...]) -> bool: ...

    I believe that his is purely a mypy bug, but someone could correct me here.

  2. KotlinIsland commented on Mar 17, 2022

    @KotlinIsland
    ContributorAuthor

    @srittau Thanks for the response!

    That stub doesn't contain anything about typing.Union.

    Could you please elaborate?

  3. AlexWaygood commented on Mar 17, 2022

    @AlexWaygood
    Member

    Strictly speaking the Union[str, int] object being passed to the union is of type typing._SpecialForm (in the stubs at least — not at runtime) and it's true that the stub doesn't currently allow for the possibility of passing in _SpecialForm objects.

    I'm not sure this is really a problem that should be solved in the stubs, though. If we add _SpecialForm to the union of types that's allowed for the second argument, that might mean that type checkers might start allowing things like isinstance(1, Literal[1]), which fails at runtime. Given that it's really only this specific special form that works with isinstance, I think it's probably better for a type checker to special-case it.

  4. KotlinIsland commented on Mar 17, 2022

    @KotlinIsland
    ContributorAuthor

    @AlexWaygood Actually it's more specifically a typing._UnionGenericAlias to typing.Union which is an instance of _SpecialForm . We could just stub a fake subtype of _SpecialForm only for Unions, and use that to annotate.

    I could make a pr when I get time.

  5. AlexWaygood commented on Mar 17, 2022

    @AlexWaygood
    Member

    I could make a pr when I get time.

    Cool, I'll happily take a look at a PR!

    In general I'm slightly leery of overcomplicating typing.pyi by reproducing the exact runtime implementation details, but if there's a way to do it simply, then sure!

  6. KotlinIsland commented on Mar 17, 2022

    @KotlinIsland
    ContributorAuthor

    Something roughly like this:

    class _SpecialForm:
        def __getitem__(self, typeargs: Any) -> object: ...
    
    class _UnionSpecialForm(_SpecialForm):
        def __getitem__(self, typeargs: Any) -> _UnionSpecialForm: ...
    
    Union: _UnionSpecialForm = ...
     def isinstance(__obj: object, __class_or_tuple: type | types.UnionType | _UnionSpecialForm) -> bool: ...
  7. KotlinIsland commented on Mar 17, 2022

    @KotlinIsland
    ContributorAuthor

    @AlexWaygood Here you go 😁 #7508

  8. changed the title [-](🐞) `isinstance` works with `typing.Union`s[/-] [+](🐞) `isinstance` works with `typing.Union`s on 3.10+[/+] on Mar 18, 2022
  9. AlexWaygood commented on Jun 12, 2022

    @AlexWaygood
    Member

    The consensus in #7508 was that this was an unusual and niche use case, so it's probably best to close this as a "wontfix".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions