Consider the following code:
def verify(schema: Type[T], data: T) -> None:
pass
verify(int, "3")
verify(float, "3")
verify(str, "3")
I would expect the first two verify()
calls to show up as a type error, and the last one to not.
However, none of them show up with type errors, in PyCharm and in mypy. I tried enabling every possible flag for strictness and error codes, yet nothing.
How can I get a type-checker to type-check this? Why does it fail?
Libraries like apischema
rely on functionality like this for type-checking, e.g., apischema.serialize(MyDataclass, my_dataclass)
, but that doesn't work either.
The type bound to T
is not determined by any single argument; it is chosen in a way that satisfies the call. Since object
is the closest common supertype of int
-and-str
or float
-and-str
), T
is bound to object
in those cases. (For verify(str, "3")
, T
is bound to str
for the same reason: it's the trivial closest supertype of str
and str
).
You can see this by having verify
return schema
, then asking mypy
what the return value has.
def verify(schema: Type[T], data: T) -> Type[T]:
return schema
reveal_type(verify(int, "3")) # builtins.object
reveal_type(verify(float, "3")) # builtins.object
reveal_type(verify(str, "3")) # builtins.str
If you can enumerate the types you expect to pass to schema
, you can partially solve the problem by making T
a constrained type variable.
T = TypeVar('T', int, str, float)
def verify(schema: Type[T], data: T) -> None:
...
Since T
can no longer be bound to object
, your first two examples will fail to type-check as desired.
Note that something like the following will still pass:
class MyFloat(float):
pass
verify(MyFloat, 3.14)
because T
will be bound to float
, the closest common superclass of float
and MyFloat
. (This isn't really any different from the case with object
, but tends to "look" less surprising.)