Question: In the Python Documentation, we find: T = TypeVar('T') # Can be anything S = TypeVar('S', bound=str) # Can be any subtype of str A = TypeVar('A', str, bytes) # Must be exactly str or bytes  We find also this code: def repeat(x: T, n: int) -> Sequence[T]: """Return a list containing n references to x.""" return [x]*n def print_capitalized(x: S) -> S: """Print x capitalized, and return x.""" print(x.capitalize()) return x def concatenate(x: A, y: A) -> A: """Add two strings or bytes objects together.""" return x + y  Adding then this line of code print(concatenate("hello ", "world"))  should be perfectly oky with mypy and it is! I define now a class: class sstr(str): pass  and use this code: s1 = sstr("hello ") s2 = sstr("world") print(concatenate(s1, s2))  In the definition of A it says: "Must be exactly str or bytes" s1 and s2 are not exactly str or bytes but sstr, which is a subclass of str. So to my mind, mypy should raise an error, but it says: Success: no issues found in 1 source file  Apparantly, I must have got something wrong. I even tried it with a class class sstr(str): def __add__(self, other): return self + other + "_42" def __sub__(self, other): return "foo"  What did I get wrong? Can anybody help?

Answer: "exactly" is misleading here. In general, the type system assumes Liskov substitutability. So a subtype S of a type T is always acceptable in the place of T. The "exactly" is referring to a specific behavior. When you use a constrained instead of bound type variable*, the result is always either str or bytes, so even if you provide a MyStr, it returns a str as far as the type checker is concerned: import typing T = typing.TypeVar("T", str, bytes) class MyString(str): pass def alphabetize(a: T, b: T) -> T: if a >= b: return a else: return b c0 = alphabetize("foo", "bar") c1 = alphabetize("foo", MyString("bar")) c2 = alphabetize(MyString("foo"), MyString("bar")) reveal_type(c0) reveal_type(c1) reveal_type(c2)  Using mypy gives: test_typing.py:20: note: Revealed type is "builtins.str" test_typing.py:21: note: Revealed type is "builtins.str" test_typing.py:22: note: Revealed type is "builtins.str" Success: no issues found in 1 source file  Here is how this is described in PEP 483, the first PEP that described how the type system was going to work and the idea of type variables:  A constrained type variable ranges only over constrains t1, etc. exactly; subclasses of the constrains are replaced by the most-derived base class among t1,  However, that isn't how it works when you give it an upper bound: import typing T = typing.TypeVar("T", bound=str) class MyString(str): pass def alphabetize(a: T, b: T) -> T: if a >= b: return a else: return b c0 = alphabetize("foo", "bar") c1 = alphabetize("foo", MyString("bar")) c2 = alphabetize(MyString("foo"), MyString("bar")) reveal_type(c0) reveal_type(c1) reveal_type(c2)  And mypy gives you: test_typing.py:20: note: Revealed type is "builtins.str" test_typing.py:21: note: Revealed type is "builtins.str" test_typing.py:22: note: Revealed type is "test_typing.MyString" Success: no issues found in 1 source file  Note, in the condition where you mix types , the result is the narrowest common type possible. Consider a situation where you have a deeper inheritance hierarchy: import typing T = typing.TypeVar("T", bound=str) class SpecialString(str): pass class SuperSpecialString(SpecialString): pass def alphabetize(a: T, b: T) -> T: if a >= b: return a else: return b c0 = alphabetize(SpecialString("foo"), SuperSpecialString("bar")) c1 = alphabetize("foo", SuperSpecialString("bar")) c2 = alphabetize(SuperSpecialString("foo"), SuperSpecialString("bar")) reveal_type(c0) reveal_type(c1) reveal_type(c2)  And here, mypy gives: test_typing.py:22: note: Revealed type is "test_typing.SpecialString" test_typing.py:23: note: Revealed type is "builtins.str" test_typing.py:24: note: Revealed type is "test_typing.SuperSpecialString