0

I am working with a generic class in python. The corresponding TypeVar is restricted to a finite amount of (invariant) types. Because it is of some importance what exact type it is at runtime, I specify the type at initialization.

Later, I have some code where I want to narrow to certain types. Because I want to narrow to this specific type, the natural way seems to use [stored_type] is [desired_type]. Unfortunately, mypy as well as pyright do not perform any narrowing.

However, if I use isinstance([stored_type], type([desired_type]), it narrows as intended. Also, a similar case where I use a Union instead, the narrowing using is works.

I am currently using isinstance because it works for the type checker, but as far as I know is provides the stronger check here and is what I actually want.

Is there a good reason for the current behaviour? Why is the narrowing with is less strict than with isinstance?

from typing import reveal_type

class MyClass1:
    def __init__(self):
        pass

class MyClass2:
    def __init__(self, arg: int):
        pass

class MyGeneric[V: (MyClass1, MyClass2)]:
    def __init__(self, member_type: type[V]):
        if member_type is MyClass1:
            reveal_type(member_type)  # type[V@MyGeneric]

        if isinstance(member_type, type(MyClass1)):
            reveal_type(member_type)  # type[MyClass1]*

    def mymethod(self, member_type: type[MyClass1] | type[MyClass2]):
        if member_type is MyClass1:
            reveal_type(member_type)  # type[MyClass1]
6
  • We don't have sealed classes in python (yet?). No one prevents your class from being used with class Child(MyClass1): ... - yes, V will still be solved to MyClass1, but the runtime type won't magically change, so your is check won't work. Commented May 4 at 14:20
  • I intend to call the __init__ method, so this would be a feature – we do not know what arguments the child class uses for __init__, so the check should only succed for the base class type Commented May 4 at 18:07
  • 1
    Then do your is comparison and then typing.cast - you can't enforce this statically in mypy. Though positive narrowing by is in this case should be safe, consider it a missing mypy feature (or perhaps even a bug, given that it works without typevar - it may be easy to implement). Please check if it's already reported at github.com/python/mypy/issues and report if not, we'll take a look. Commented May 4 at 18:14
  • I was going to suggest using a protocol as then you would know the call signature of the class's __init__ func. However, it seems mypy fails to detect when a child class changes the interface for __init__ such that it no longer an adhere to the protocol specification. Commented May 4 at 18:40
  • Have you considered requiring your types to provide @classmethod factory functions? That way you could sure of a safe way to create instances of the type. Commented May 4 at 18:59

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.