Python Type Hints – How to Handle Optional Imports
adamj.eu
adamj.eu
It’s an uphill battle to convert a team over to using hinting because of how awkward things can get and how easy it is to just pretend the feature doesn’t exist.
In general I've yet to see what these type check systems (be it mypy, pyright or any other) offer over proper tests. Type hints are there for developers not the machine.
E.g., when I write numerical python code I'll often just use a string like `"(n_items, n_dims)"` as the type hint, or if the only salient detail of a signature is that some dimensions match I might even write something as simple as `foo(bar: "(a, b)", baz: "(b, c)")`. Ofttimes that's the only data a developer really cares about in that programming context, so a more complete type hint (one designed for mypy and the rest of the tooling) would just take longer to read and understand.
Most projects don't need it and I can't help but facepalm when I see small projects have half of their commits describing typing compliance battles just because mypy came with "yet another python project boilerplate" or pressure from the industry.
In a language with proper first-class typing, the advantage is that they're included in your automated refactoring rather than needing manual updates. But yeah I've never found optional type systems to be any use.
The only way for type hints not to be static types is to not run the checker before deploying.
Static typing means that the types of variables and their values are statically checked (hence the name) before the execution; in most statically typed languages that happens at compile time. There is no static check at runtime, and it's perfectly possible to send an incorrect type to a dynamically loaded library even in compiled languages (which usually, but not necessarily, results in crashing he program).
If you want to see where static typing really shines, try writing some Haskell. It's an absolute delight.
Only if the type hints for all the libraries you use are correct (and in practice they're not).
The import pattern in the article is actually something I’ve never encountered because of how common the HAS_MODULE pattern is. I’ve never even thought to override the module binding.
What a mess python can be, it is spectacular in this way. While it's possible with any tool, this deserves a special category award:
I hereby nominate thee probable annual 2022 winner of:
HN Kludge of the Year
With PyTorch AI derived confidence of 0.99999With confidence that high, it might be a runner up for kludge of the year.
try:
import zoneinfo
except:
from backports import zoneinfo
The solution that I found to make mypy happy is: import sys
if sys.version_info >= (3, 9):
import zoneinfo
else:
from backports import zoneinfo
I have no idea why mypy is happy.If I can get type hinting to work, it does detect certain errors in certain situations. But the whole thing takes too much work. The syntax is awkward and klunky. I'm not always sure that mypy actually catches typing errors, even with the --strict flag. And Python package management is already a shit show, but when I add mypy to the mix, it spews incomprehensible error messages that I have no idea how to fix.
I'm starting to look at Golang to replace my Python usage. If I'm going to do the work of adding typing info, I'd rather do it in a language that has static type checking from the very beginning. And being able to compile down to a single binary is appealing.
The first import can fail for any number of reasons apart from the expected one and that reason will be swallowed and potentially replaced by a different, misleading, second order error message or worse - strange hard to explain behavior.
The second will behave (and more importantly, fail) in a much more predictable way.
(For anyone else scratching their head, it's Pokemon exception handling cause the except block catches any and all exceptions (gotta catch 'em all).)
If you're targeting multiple Python versions your CI should be running mypy multiple times.
I likewise switched to D and Rust and (excepting the difference in ecosystems richness compared to Python) am easily equally (or more) productive in statically typed languages.
from types import ModuleType
from typing import cast
try:
import markdown
except ImportError:
markdown = cast(ModuleType, None) from types import ModuleType
markdown: ModuleType | None
try:
import markdown # type: ignore [no-redef]
except ImportError:
markdown = None
if markdown is not None:
reveal_type(markdown.markdown)
Running Mypy: example.py:11: note: Revealed type is "Any"
Whereas when correctly imported: example.py:3: note: Revealed type is "def (text: builtins.str, *, extensions: Union[typing.Sequence[Union[builtins.str, markdown.extensions.Extension]], None] =, extension_configs: Union[typing.Mapping[builtins.str, typing.Mapping[builtins.str, Any]], None] =, output_format: Union[Literal['xhtml'], Literal['html'], None] =, tab_length: Union[builtins.int, None] =) -> builtins.str"