Home > Software engineering >  Cannot cast to class imported on condition of TYPE_CHECKING
Cannot cast to class imported on condition of TYPE_CHECKING

Time:07-11

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())
  • Related