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

后端 未结 2 1731
暖寄归人
暖寄归人 2021-01-13 15:29

I\'m struggling to get my head around the difference between the following two TypeVars

from typing impor         


        
2条回答
  •  庸人自扰
    2021-01-13 16:08

    After a bunch of reading, I believe mypy correctly raises the type-var error in the OP's question:

    generics.py:31: error: Value of type variable "T" of "X" cannot be "AA"

    See the below explanation.


    Second Case: TypeVar("T", bound=Union[A, B])

    I think @Michael0x2a's answer does a great job of describing what's happening. See that answer.


    First Case: TypeVar("T", A, B)

    The reason boils down to Liskov Substitution Principle (LSP), also known as behavioral subtyping. Explaining this is outside the scope of this answer, you will need to read up on + understanding the meaning of invariance vs covariance.

    From python's typing docs for TypeVar:

    By default type variables are invariant.

    Based on this information, T = TypeVar("T", A, B) means type variable T has value restrictions of classes A and B, but because it's invariant... it only accepts those two (and not any child classes of A or B).

    Thus, when passed AA, mypy correctly raises a type-var error.


    You might then say: well, doesn't AA properly match behavioral subtyping of A? And in my opinion, you would be correct.

    Why? Because one can properly substitute out and A with AA, and the behavior of the program would be unchanged.

    However, because mypy is a static type checker, mypy can't figure this out (it can't check runtime behavior). One has to state the covariance explicitly, via the syntax covariant=True.

    Also note: when specifying a covariant TypeVar, one should use the suffix _co in type variable names. This is documented in PEP 484 here.

    from typing import TypeVar, Generic
    
    class A: pass
    class AA(A): pass
    
    T_co = TypeVar("T_co", AA, A, covariant=True)
    
    class X(Generic[T_co]): pass
    
    class XA(X[A]): pass
    class XAA(X[AA]): pass
    

    Output: Success: no issues found in 1 source file


    So, what should you do?

    I would use TypeVar("T", bound=Union[A, B]), since:

    • A and B aren't related
    • You want their subclasses to be allowed

    Further reading on LSP-related issues in mypy:

    • python/mypy #2984: List[subclass] is incompatible with List[superclass]
    • python/mypy #7049: [Question] why covariant type variable isn't allowed in instance method parameter?
      • Contains a good example from @Michael0x2a

提交回复
热议问题