[Tutor] Type hint question
Oscar Benjamin
oscar.j.benjamin at gmail.com
Thu Sep 26 13:50:25 EDT 2024
On Thu, 26 Sept 2024 at 18:12, Albert-Jan Roskam
<sjeik_appie at hotmail.com> wrote:
>
> Hi,
> I just started using type hints and mypy so this is probably a basic
> question. Why doesn't like mypy the code below? Also, I noticed that mypy
> seems to favour LBYL (if isinstance) to EAFP (try-except), in the sense
> that doesn't understand the latter well. Is this true?
Sort of if by EAFP you mean that you'll call code that is not
necessarily valid for the type and then catch the exceptions for the
wrong type. The type checker checks that the arguments are used as
expected though so if you mean to catch TypeError somewhere then it
suggests that your types are not consistent somehow.
> I'm still learning
> to use mypy, but sometimes it feels a lot like "pleasing mypy". Then
> again, it has already helped me find some errors.
>
> from typing import TypeVar
> T = TypeVar("T")
> def try_int(value: T) -> T:
> try:
> return int(value.strip(" "))
> except (TypeError, AttributeError, ValueError):
> return value
>
> n = try_int(" 1")
> I get this message:
> main.py:7: error: Incompatible return value type (got "int", expected "T")
> [return-value]
> main.py:7: error: "T" has no attribute "strip" [attr-defined]
> Found 2 errors in 1 file (checked 1 source file)
Those errors are reasonable.
You have declared the parameter and return types to both be T. That
means that this should return the same type as the input although note
that "type" here is not exactly the same as "type" in the usual Python
sense although usually it means the same thing.
You are calling the function with a string and it will return an int.
This is not consistent with saying that the return type is the same as
the parameter type unless T is a sufficiently general type that both
str and int are subtypes of T.
Your function also returns different types, int or T, depending on the
codepath which is broadly not the kind of thing that you should expect
a typechecker to be happy with.
I think here that mypy would expect you to change this a bit like:
def try_int(value: T) -> T | int:
if isinstance(value, str):
try:
return int(value.strip())
except ValueError:
pass
return value
So the return value type is either the same as the input (T) or it is
an int. You also don't call the .strip() method without first checking
that you actually have a string which can be checked by the parameter
type hint or isinstance() here since the parameter is T and not str.
Personally I wouldn't use "EAFP" like this for TypeError. Catching the
ValueError for a string whose value might not be valid seems
reasonable but types I would check explicitly whether with a
TypeChecker or isinstance() and I would only use isinstance() at the
boundaries. Internal code should be able to work with the expected
types without the need for runtime checks (a typechecker helps to
ensure this).
I would say then that the problem you have with this function is not
mypy being picky but you defining a poorly typed function. Clearly the
argument is supposed to be a string but it is being called with things
that are not strings. That suggests other problems elsewhere like the
code calling this function also should be changed. There are ways to
make the typechecker accept this but really the idea would be to avoid
writing code like this.
--
Oscar
More information about the Tutor
mailing list