14

I'm using mypy and the typing module in python. Imagine I have a generic type:

ContainerT = TypeVar('ContainerT')

class Thingy(Generic[ContainerT]):
    pass

However I want to get at another type inside the concrete type associated with the TypeVar like in the example below:

class SomeContainer(object):
    Iterator = SomeCustomIterator

ContainerT = TypeVar('ContainerT')

class Thingy(Generic[ContainerT]):
    def foo(self) -> ContainerT.Iterator:
        pass

I get an error saying that TypeVar does not have a member Iterator. Is there another way to reformulate this so I can associate one type with another?

Thank you.

Alexander Kondratskiy
  • 4,156
  • 2
  • 30
  • 51
  • I don't think you can have proper associated types, but maybe you can make the iterator another generic type parameter on Thingy and somehow constrain which combinations are valid? – Markus Unterwaditzer Jun 02 '20 at 11:20

1 Answers1

2

Maybe you are thinking about something like this.

from __future__ import annotations

from abc import abstractmethod
from typing import Generic, TypeVar, Iterator, Protocol

_Iterator = TypeVar('_Iterator', bound=Iterator, covariant=True)

class SomeContainer:
    iterator: Iterator[int]

    def __init__(self, iterator: Iterator[int]):
        self.iterator = iterator

    def get_iterator(self) -> Iterator[int]:
        return self.iterator


class SomeContainerProtocol(Protocol[_Iterator]):
    @abstractmethod
    def __init__(self, iterator: _Iterator):
        pass

    @abstractmethod
    def get_iterator(self) -> _Iterator:
        pass


_SomeContainer = TypeVar('_SomeContainer', bound='SomeContainerProtocol', covariant=True)


class Thingy(Generic[_SomeContainer]):
    container: _SomeContainer

    def __init__(self, container: _SomeContainer):
        self.container = container

    def foo(self: Thingy[SomeContainerProtocol[_Iterator]]) -> _Iterator:
        pass

    def bar(self) -> _SomeContainer:
        pass

thingy = Thingy(SomeContainer(range(10).__iter__()))
reveal_type(thingy) # Revealed type is '...Thingy[...SomeContainer*]'
reveal_type(thingy.foo) # Revealed type is 'def () -> typing.Iterator[builtins.int]'
reveal_type(thingy.bar) # Revealed type is 'def () ->  ...SomeContainer*'

You can annotate self with a generic protocol (available starting Python 3.8) in place of your type var and mypy will then infer the type of the iterator.

unique2
  • 2,162
  • 2
  • 18
  • 23