The following code is stored in a file called sample.py.
import re
from typing import Optional, Tuple
def func(path: str) -> Optional[Tuple[str, str]]:
regex = re.compile(r"/'([^/']+?)'/'([^/']+?)'")
try:
return regex.match(path).groups()
except AttributeError:
return None
The Mypy Python linter throws the following error when analyzing the code:
sample.py:8: error: Incompatible return value type (got "Union[Sequence[str], Any]", expected "Optional[Tuple[str, str]]")
sample.py:8: error: Item "None" of "Optional[Match[str]]" has no attribute "groups"
While regex.match(path).groups()
may return a None
type, which does not have a groups
attribute, the resulting exception is handled and the handling is specified in the return type. However, Mypy does not seem to understand that the exception is being handled. As far as I understand Optional[Tuple[str, str]]
is the correct return type and Mypy instead insists that the less specific type Union[Sequence[str], Any]
is correct . What is the proper way to use exception handling with Python typing? (Please, note that I am not asking for alternate ways to write the code without using exception handling. I am just trying to provide a minimal and complete example, where Python type checkers do not behave as I would expect with exception handling.)
How to Fix TypeError in Python. To avoid type errors in Python, the type of an object should be checked before performing an operation. This can help ensure that the object type is appropriate for the operation and if the operation is supported by the object.
print_exc() to print the current exception to standard error, just like it would be printed if it remained uncaught, or traceback. format_exc() to get the same output as a string. You can pass various arguments to either of those functions if you want to limit the output, or redirect the printing to a file-like object.
TypeError is raised whenever an operation is performed on an incorrect/unsupported object type. For example, using the + (addition) operator on a string and an integer value will raise TypeError.
Common Examples of Exception:Accessing a file which does not exist. Addition of two incompatible types. Trying to access a nonexistent index of a sequence.
Mypy does not really understand exceptions on a deep level -- in this case, does not understand that since you're catching the AttributeError, it can ignore the "what if regex.match(path)
is None?" case.
More generally, the fundamental assumption mypy makes is that when you have some object foo
with type Union[A, B]
and you do foo.bar()
, both types A
and B
have a bar()
method.
If only one of those types have a bar()
method, you'll need to do one of several things:
x is not None
checks...# type: ignore
comment, find a way of making foo
be the dynamic Any
type...(In this particular case, I suppose another alternative might be to submit a pull request to mypy adding support for this pattern. But I'm not sure if this is really feasible: changing any kind of fundamental assumption is difficult work on multiple dimensions.)
Similarly, Mypy also does not understand regexes on a deep level -- e.g. doesn't try and analyze your regex to determine how many groups you'll get and so won't understand your particular regex happens to match strings with exactly two groups. The best it can do is assert that the group will return some unknown number of strings -- hence the type Sequence[str]
instead of Tuple[str, str]
.
This sort of limitation is pretty common in type checkers in general, actually: most type systems in mainstream languages don't really support a way to predicate a return type based on the contents of any actual values passed in. Such type systems (dependent type systems, refinement type systems...) are pretty difficult to implement and often have a steep learning curve for end users.
However, it would be easier to make mypy support this on a best-effort basis by writing a mypy plugin, if you're up for it. Specifically, try taking a look at get_method_hook()
and get_function_hook()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With