What does Typevar’s covariant and contravariant of the Typing module mean?

Asked

Viewed 71 times

4

When using the module typing Python, available in 3.5+ versions, it is possible to define new types using the structure TypeVar.

from typing import TypeVar

T = TypeVar('T')  # Pode ser qualquer tipo
A = TypeVar('A', str, bytes)  # Necessita ser uma string ou sequência de bytes

In addition, it is possible to define the type as covariant or contravariant.

T = TypeVar('T', covariant=True)
T = TypeVar('T', contravariant=True)

What are these two options for the new type? What are the peculiarities of each and when to (can) use them?

1 answer

3


Imagine that we are programming a game. Our game features several three-dimensional objects, including cardboard boxes. A way to represent that in code would be like this:

class Figura3D():
    pass

class Cubo(Figura3D):
    pass

class CaixaDePapelao(Cubo):
    pass

There is a base class to represent all three-dimensional figures, then a class to represent all three-dimensional cubic figures, then a class to represent specifically a cardboard box, which is obviously a three-dimensional cubic figure.

Our game needs a class responsible for rendering things on the screen, which would look something like this:

T = TypeVar('T')

class Renderizador(Generic[T]):
    def __init__(self, x: T):
        pass

Notice that the class Renderizador has a generic argument of type T, because he should be able to render anything.

The code of our game still has a function that specifically serves to render cubes.

def executar_render(render: Renderizador[Cubo]):
    pass

It receives a cube renderer and uses it to draw the cube on the screen.

Now, knowing that an object of the class CaixaDePapelao is also a Cubo (due to inheritance), it makes sense to pass a Renderizador[CaixaDePapelao] for the above function, correct?

Not!

The following code is refused by mypy:

render_caixa_de_papelao = Renderizador(CaixaDePapelao())
executar_render(render_caixa_de_papelao)

error: Argument 1 to "executar_render" has incompatible type "Renderer[Caixadepapelao]"; expected "Renderer[Cubo]"

This happens because the TypeVar('T') by default is invariant. That means your subclasses and superclasses are not compatible with it. However, if we change the statement to:

T = TypeVar('T', covariant=True)

The error disappears and mypy accepts that cardboard box renderers are used instead of cube renderers.

In short:

  • A guy invariant does not accept subclasses or superclasses
  • A guy covariant accepts subclasses, but not superclasses
  • A guy countervariant accepts superclasses, but not subclasses

Now the question remains:

What are the names of the guys who accept both subclasses and superclasses?

Are called bivariate and are not supported by Python.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.