1. Context
I often import python modules only for the type-checker, like mypy
. For example:
if TYPE_CHECKING:
import bar
I would then use module bar
in type hints throughout the code. These type hints are only visible for type-checkers, while bar
itself won't be imported when running the code live.
Giving type hints this way works just fine. However, when attempting to cast types, I get a 'module not found' error.
2. Example
Let's consider the following example. I have three modules/files:
test.py
foo.py
bar.py
This is test.py
:
# FILE: TEST.PY
# =============
from typing import *
import foo
if TYPE_CHECKING:
import bar
def my_func():
foo_obj:foo.Foo = foo.Foo()
bar_obj:bar.Bar = foo_obj.get_something()
The type-checker mypy
complains about the last line. Now let us take a look at the function get_something()
in foo.py
to figure out why:
# FILE: FOO.PY
# =============
import bar
class Foo:
def __init__(self) -> None:
...
return
def get_something(self) -> Union[bar.Bar, str]:
...
return something
The type checker mypy
knows that get_something()
returns either a Bar()
-object or a string. Therefore, mypy
complains. Fair enough.
Now I know for a fact that - in this particular situation - get_something()
will return a Bar()
-object. The type checker mustn't worry. My first reflex therefore would be to give a hint to the type checker with an assertion or a cast:
# ASSERTION:
# Give hint to type checker with an assertion
something = foo_obj.get_something()
assert isinstance(something, bar.Bar)
bar_obj:bar.Bar = something
# CAST:
# Give hint to type checker with a cast
bar_obj:bar.Bar = cast(foo_obj.get_something(), bar.Bar)
The cast approach looks the most elegant to me, because it can be done with minimal overhead. Also, the cast has no runtime effects. At runtime, the cast()
function is essentially a nop()
. Only type-checkers see the cast.
3. The problem
Unfortunately, the cast throws a 'module not found' error at runtime. That's not what I expected it to do. After all, at runtime casts should be totally ignored! I see a few ways to deal with this, but they all have their downsides:
Import module
bar
unconditionally.- Pro: Now the module can be used in casts without throwing an error.
- Contra: I must import modules only for casting. That feels wrong. It's suboptimal, and sometimes it causes circular imports.
Ignore the type-checker's error with a
# type: ignore
comment- Pro: I don't need to cast.
- Contra: Silencing the type checker on a line-by-line basis is a last-resort solution. I prefer something cleaner.
Do you know how to tell the type-checker mypy
- other than a cast or assertion - that foo_obj.get_something()
will return a Bar()
-object in this situation? Or is there a way to do a cast without crashing the code at runtime (because of a missing module)?
CodePudding user response:
Generally speaking, you can pass type names to any of the typing
functions as strings. mypy
will be smart enough to do the resolution, and the Python runtime won't complain since it's just a string. So you can do the cast like this.
bar_obj = cast('bar.Bar', foo_obj.get_something())