Python Type Hints - How to Handle Optional Imports
This post is not about importing
typing.Optional, but instead imports that are themselves optional. Libraries often have optional dependencies, and the code should work whether or not the import is there. A common pattern to solve this to catch
ImportError and replace the module with
try: import markdown except ImportError: markdown = None
Later, code that may use the module checks if the name is not
def do_something(): ... if markdown is not None: ...
This pattern works fine—Python has no qualms. But, Mypy does. If you run Mypy on the above
try block, it will report:
$ mypy example.py example.py:4: error: Incompatible types in assignment (expression has type "None", variable has type Module)
Oh gosh! Mypy sees the
import statement and infers that
markdown has the type
ModuleType only. It therefore doesn’t allow assignment of
None to such a variable.
Potentially, a future version of Mypy could detect this pattern, and instead infer that
markdown has type
ModuleType | None. Version 0.920 included a similar change, to allow a
None assignment in an
else block to affect a variable’s inferred type. (See “Making a Variable Optional in an Else Block” in the release post.) But, at least at time of writing, you have to use a different pattern.
The solution I’ve found is to use a second
bool variable to indicate whether the module imported:
try: import markdown HAVE_MARKDOWN = True except ImportError: HAVE_MARKDOWN = False def something(): ... if HAVE_MARKDOWN: ...
Mypy is just fine with this:
$ mypy example.py Success: no issues found in 1 source file
Well, that’s the trick. Go make your optional imports work.
Some readers may have wondered if it’s possible to pre-declare the type of
markdown before its
from types import ModuleType markdown: ModuleType | None try: import markdown except ImportError: markdown = None
Unfortunately, Mypy does not allow
import to replace a pre-declared variable:
$ mypy example.py example.py:6: error: Name "markdown" already defined on line 3
Aw, shucks. It seems Mypy’s interpretation of
import is pretty strict. It probably is strict with good reason, as it uses import statements to fetch related type hints.
Learn how to make your tests run quickly in my book Speed Up Your Django Tests.
One summary email a week, no spam, I pinky promise.
- Python Type Hints - Use Cases for the types Module
- Python Type Hints - How to Vary Return Type Based on an Argument
- Python Type Hints - How to Avoid “The Boolean Trap”
Tags: mypy, python