lorg
lorg

Reputation: 1170

Python Generic that creates or isinstance checks their type parameter

I want to create an inheritable class that can be parameterized with a type. Here is a working example without type annotations:

class Simple:
    pass

class Bla:
    obj_class = Simple

    def do(self):
        return self.obj_class()

    def check(self, x):
        return isinstance(x, self.obj_class)

Users of this code would inherit from Bla, and can set a different obj_class, like so:

class Advanced(Simple):
    pass

class Foo(Bla):
    obj_class = Advanced

The problem starts when I want to correctly type annotate this. I thought of making Bla inherit from Generic[T], where T is defined as TypeVar('T', bound=Simple), but then the constructor T() and isinstance won't work, and also manually assigning a different class to obj_class also doesn't work.

Here is one non-working example, as T can't be used in non-typing contexts:

class Simple:
    pass

T = TypeVar('T', bound=Simple)

class Bla(Generic[T]):
    def do(self) -> T:
        return T()

    def check(self, x: Any) -> bool:
        return isinstance(x, T)

Here is another non-working example, where I can't assign Simple to obj_class because of incompatible types.

class Simple:
    pass

T = TypeVar('T', bound=Simple)

class Bla(Generic[T]):
    obj_class: Type[T] = Simple

    def do(self) -> T:
        return self.obj_class()

    def check(self, x: Any) -> bool:
        return isinstance(x, self.obj_class)

class Advanced(Simple):
    pass

class Foo(Bla):
    obj_class = Advanced

Is there a way to solve this?

Upvotes: 3

Views: 2331

Answers (1)

Dani Schechter
Dani Schechter

Reputation: 96

You don't need Type[T] = Simple.

mypy states:

Incompatible types in assignment (expression has type "Type[Simple]", variable has type "Type[T]").

You are trying to assign a concrete type to a generic type variable.

Instead, do something like:

class Simple:
    pass


class Advanced(Simple):
    pass


class Other:
    pass


T = TypeVar('T', bound=Simple)


class Bla(Generic[T]):
    obj_class: Type[T]

    def do(self) -> T:
        return self.obj_class()

    def check(self, x: Any) -> bool:
        return isinstance(x, self.obj_class)


class SimpleFoo(Bla[Simple]):
    obj_class = Simple


class AdvancedFoo(Bla[Advanced]):
    obj_class = Advanced


class OtherFoo(Bla[Other]):
    obj_class = Other

Now, mypy correctly states:

error: Type argument "tmp.Other" of "Bla" must be a subtype of "tmp.Simple"

Note

OtherFoo has to subclass Bla with a specific type so mypy will correctly warn you.

The following produces no errors:

class OtherFoo(Bla):
    obj_class = Other

Upvotes: 8

Related Questions