Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python typing what does TypeVar(A, B, covariant=True) mean?

Today I took a deep dive into Liskov's Substitution Principle and covariance/contravariance.

And I got stuck on the difference between:

  1. T = TypeVar("T", bound=Union[A, B])
  2. T = TypeVar("T", A, B, covariant=True)

My Understanding of #1

Difference between TypeVar('T', A, B) and TypeVar('T', bound=Union[A, B])

This answer clearly states T can be :

  1. Union[A, B] (or a union of any subtypes of A and B such as Union[A, BChild])
  2. A (or any subtype of A)
  3. B (or any subtype of B)

This makes perfect sense to me.


My Flawed Understanding of #2

MyPy doesn't allow constrained TypeVar's to be covariant? Defining a generic dict with constrained but covariant key-val types

Re-mentions the bound=Union[A, B] case, but does not get at the meaning of option #2, A, B, covariant=True.

I have tried playing around with mypy, and can't seem to figure it out. Can anyone point out what this means?

I think it means:

  1. A (or any subtype of A)
  2. B (or any subtype of B)

(aka it excludes the Union case from above)


**Edit**

It was asked in the comments:

Are you sure that they're actually different?

Here's sample code to show the difference. The errors come from mypy==0.770.

from typing import Union, TypeVar, Generic


class A: pass

class ASub(A): pass

class B: pass


# Case 1... Success: no issues found
# T = TypeVar("T", bound=Union[A, B])

# Case 2... error: Value of type variable "T" of "SomeGeneric" cannot be "ASub"
T = TypeVar("T", A, B, covariant=True)


class SomeGeneric(Generic[T]): pass

class SomeGenericASub(SomeGeneric[ASub]): pass

**Edit 2**

I ended up asking about this in python/mypy #8806: Generic[T_co] erroring when T_co = TypeVar("T_co", A, B, covariant=True) and passed subclass of A

This cleared up some misunderstandings I had. Turns out TypeVar("T", A, B, covariant=True) isn't really correct, knowing the value restrictions A and B aren't actually covariant.

Use of covariant=True syntax is only helpful when they're related.

like image 538
Intrastellar Explorer Avatar asked May 03 '20 01:05

Intrastellar Explorer


People also ask

What is Typevar in Python?

It is a type variable. Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function definitions.

What is type checking in Python?

Python is a dynamically typed language. This means that the Python interpreter does type checking only as code runs, and that the type of a variable is allowed to change over its lifetime.


1 Answers

Covariance and contra-variance are terms that relate to the intersection between object orientation and generics.

Here's the question this concept is trying to answer:

  1. We have a couple of "regular", "object-oriented" classes, Base and Derived.
  2. We also have some generic type - let's say List<T>.
  3. We know that Derived can be used anywhere base can - does that mean that List<Derived> can be used wherever List<Base> can?
  4. Could it be the other way around? Maybe it's the reverse direction and now List<Base> can be used wherever List<Derived> can?

If the answer to (3) is yes, it's called covariance and we'll say declare List as having covariance=True. If the answer to (4) is true, it's called 'contra-variance'. If none is true, it's invariant.

Bounds also come from the intersection of OO and generics. When we define a generic type MyType - does it mean that 'T' can be any type at all? Or, may I impose some limitations on what T might be? Bounds allow me to state that the upper bound of T is, for example, the class Derived. In that case, Base can't be used with 'MyType' - but Derived and all its subclasses can.

The definition of covariance and contravariance can be found in this section of PEP-484.

like image 73
Roy2012 Avatar answered Sep 19 '22 02:09

Roy2012