Re: Type hint question
Oscar Benjamin via Tutor <[email protected]> Thu, 26 Sep 2024 18:50:25 +0100
| Newsgroups | gmane.comp.python.tutor |
|---|---|
| Message-ID | <CAHVvXxRD5WqFE+0cnptGCGNTh+EU8LZATD5Mp8b=tTH4cT_AGw@mail.gmail.com> |
On Thu, 26 Sept 2024 at 18:12, Albert-Jan Roskam <[email protected]> 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 _______________________________________________ Tutor maillist - [email protected] To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor