Type hints rule!
Introduction, with an obligatory nod towards Ada

I had fun using
๐คฉ Ada
๐คฉ
at work a while,
especially when I started writing some Serious Software™.
However, someone else might have to work on the same software one day,
so they “asked” me to rewrite what I’d started in Python
and proceed from there.
✗The irony of this situation goes
many levels deep,
but with a Herculean effort we’ll stay on topic.
Instead, I’ll admit how it’s actually convenient:
one library I need to use is not readily available in Ada,
whereas Python is one of its two default API’s.
pip install unusually_useful_library
and we’re off to the races, nice!
Technically, then, I can’t complain,
but I can still grumble, at least a little.
๐ญ
Oh come on, it’s not
that bad.
I’ve been using Python on and off for something like 15 years,
mostly because other people compel me to,
but the language has its appeal.
Take, for instance, its unusual approach to scope:
neither unsightly curly braces,
nor beautiful
begin
/
end
pairs,
but
indentation itself:
for each in range(10):
print(each)
I thought it was crazy when I first heard of it,
but I kind of like it now.
Kind of.
The one thing I didn’t like
One thing I did dislike about Python was the lack of static type-checking.
✝If you don’t understand why
assert(isinstance(x, MyClass))
doesn’t cut it, keep reading.
That’s burned me far too many times, and I know I’m not alone.
I tried Cython, which promises speed improvements in exchange for type annotations on variables,
but it didn’t help at all, because the real problem was with the data structures.
Neverthemore, static type-checking can be useful.
Even Python’s Powers-That-Be recognize that,
allowing the programmer first to annotate code with types.
Alas, that’s
all they did.
Python by itself doesn’t actually do anything with the types,
so the annotation is mostly useless unless you think the reader will pay attention.
Sadly, most Python programmers I’ve met aren’t aware type hints exist.
✛To be fair, it is an extremely small sample
size.
Enter the mypy
Fortunately, tools exist that let you do more than merely annotate:
Comes now
mypy to save the day!
Write yourself a program, or even a whole set of modules,
pass it to mypy, and voilà!
Lines and lines of errors alerting you
to (some of) the mistakes that will crash your program at runtime.
Better yet, the error messages are brief and comprehensible!
✠If only
the C++ compiler devs would realize that error messages
have more purpose in life than intimidating the weak.
Then again, a legible C++ program is a contradiction in terms,
so it’s hardly a surprise that the compiler developers took their cues
from the languaeg designers.
An example of questionable merit
Here’s a relatively simple example that shows
(a) what a terrible programmer I am, and
(b) how mypy makes me less terrible.
Consider the following file,
my_class.py
:
class MyClass:
def __init__(self, value: float) -> None:
self._field: int = value
def get_value(self) -> float:
return self._field
Let’s ๐ง mypy ๐ง that bad boy:
$ mypy my_class.py
my_class.py:4: error: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment]
Look at that! I didn’t have to run the program to find and fix a mistake;
mypy found it for me!
Sure, that’s a stupid mistake, and
sure, someone who makes it in a program as simple as this should perhaps reconsider his career,
but it happens a lot, especially when a class has many fields, and as a program evolves.
You start off thinking you need a
float
, but it turns out
you want to use the field in a library that only accepts
int
s,
or vice versa. Go ahead and try to index that array with a
float
and see what happens.
A fix
Let’s fix that. Change the field so that it is an integer:
def __init__(self, value: int) -> None:
Run ๐ง mypy ๐ง again:
$ mypy my_class.py
Success: no issues found in 1 source file
What?!?
That disappoints me a little: the function
get_value
promises a
float
but returns an
int
.
My Ada- and Rust-accustomed brain sees that as an error,
but in Python it’s the correct answer, because
Python automatically promotes
int
s
to
float
s.
An example of unquestionable merit
Let’s illustrate one more error that’s bitten me more times than I care to admit.
Consider the following file,
use_my_class.py
.
from my_class import MyClass
my_instance: MyClass = MyClass()
print(my_instance.get_value + 1)
First error
Throw mypy at it, and an error occurs right off:
$ mypy use_my_class.py
use_my_class.py:3: error: Missing positional argument "value" in call to "MyClass" [call-arg]
Found 1 error in 1 file (checked 1 source file)
Again, an obvious error, easily fixed: just put any integer at all between those parentheses.
I’m somewhat partial to 4 for some reason,
so I’ll change it
MyClass(4)
.
Second error
Everything’s good now, right? Nope:
$ mypy use_my_class.py
use_my_class.py:4: error: Unsupported operand types for + ("Callable[[], float]" and "int") [operator]
Found 1 error in 1 file (checked 1 source file)
Whoops!
Pat yourself on the back if you spotted that error before I ran mypy on it,
as it’s hard to spot but easy to fix.
If you’d tried to run it, it would have raised an exception:
$ python use_my_class.py
Traceback (most recent call last):
File "/home/cantanima/website/living_journal/use_my_class.py", line 4, in
print(my_instance.get_value + 1)
~~~~~~~~~~~~~~~~~~~~~~^~~
TypeError: unsupported operand type(s) for +: 'method' and 'int'
The corrected program should be:
from my_class import MyClass
my_instance: MyClass = MyClass(4)
print(my_instance.get_value() + 1)
mypy signs off on it this time, and it runs great in Python!
$ python use_my_class.py
5
Again, it might seem silly in this example, but when you have a large program
you may well not see it. Worse, you can be very far deep into actual work
before the bug will manifest itself, crash your program by raising an exception,
and waste all the work you did since you last saved.
(You
did save, didn’t you?
๐คจ )