Into and From interfaces: struggling with typing.Type's covariance

Viewed 197

I'm trying to implement an interface for conversions between types but I'm struggling to make it consistent since typing.Type is covariant

U = TypeVar('U')


class Into(Protocol[U]):
    @abstractmethod
    def into(self, t: Type[U]) -> U:
        pass

The docs give a similar example with a crucial difference

class User: ...
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...

def make_new_user(user_class: Type[User]) -> User:
    return user_class()

There they say type checkers should check that all subclasses of User should implement a constructor with a valid signature to be instantiated like this. My use case is different because I might not be constructing the new type, just returning a pre-existing one. Say I do

class X: pass

class Wrapper:
    def __init__(self, x: X):
        self._x = x

    def into(self, t: Type[X]) -> X:
        return self._x

that all works fine until someone subclasses X

w = Wrapper(X())
...
class XX(X): pass
x: XX = w.into(XX)

The RHS is fine by mypy cos Type is covariant, but clearly the API is broken cos an X isn't an XX. If Type wasn't covariant this wouldn't be a concern: the RHS wouldn't type check until Wrapper was updated to support XX.

My question is: Is there some way to achieve this (or something similar) given the covariance of Type?

Context

I want to use this to convert a type to multiple other types, specifying the desired type explicitly rather than just into_X, into_Y etc. I expect to do this with TypeVar or overload. I'm also having difficulty there.

This is inspired by rust's Into, where t: Type[U] is a type parameter not a function argument.

1 Answers

eval solves the issue.

class Wrapper:
    def __init__(self, x: X):
        self._x = x

    def into(self, t: Type[X]) -> X:
        return eval(f'{t}({self._x})')
Related