2023-08-14
A brief brush with writing and using Python type hints
I was recently nerd sniped into writing a Python version of a
simple although real exercise. As part
of that nerd snipe, I decided to write my Python using type hints
(which I've been tempted by for some time).
This is my first time really trying to use type hints, and I did
it without the benefit of reading any 'quick introduction to Python
type hints' articles; I worked from vague memories of seeing the
syntax and reading the documentation for the standard library's
typing
module.
I checked my type hints with mypy, without doing anything particularly
fancy.
Looking at what I wrote now, I see I missed one trick through ignorance, which is how to declare attributes of objects. I wrote:
class X: def __init__(self) -> None: self.known_tests: list[str] = []
The idiomatic way of doing this is apparently:
class X: known_tests: list[str] def __init__(self) -> None: self.known_tests = []
I believe that mypy can handle either approach but the second is what I've seen in some recent Python articles I've read.
The declaration for '__init__
' is another thing that I had to
stumble over. Initially I didn't put any type annotations on
'__init__
' because I couldn't see anything obvious to put there,
but then mypy reported that it was a method without type annotations.
Marking it explicitly as returning None
caused mypy to be happy.
While writing the code, as short and trivial as it is, I know that I made at least one absent-minded mistake that mypy's type checking would have caught. I believe I made the mistake before I fully filled out the types, so it's possible that simply filling them out would have jogged my mind about things so I didn't slip into the mistake. In either case, having to think about types enough to write them down feels useful, on top of the type checking itself.
At the same type, typing out the types felt both bureaucratic and verbose. Some of this is because my code involves several layers of nested containers; I have tuples inside lists and being returned by a generator. However, I don't think this is too unusual, so I'd expect to want to define a layer of intermediate types in basically anything sophisticated, like this:
logEntryType = tuple[str, typing.Any]
This name exists only to make type hints happy (or, to put it the
other way, to make them less onerous to write). It's not present
in the code or used by it. Possibly this is a sign that in type
hint heavy code I'd wind up wanting to define a bunch of small data
only dataclasses,
simply so I could use these names outside of type hints. This makes
me wonder if retrofitting type hints to already written code will
be somewhat awkward, because I'd wind up wanting to structure the
data differently. In code without type hints, slinging around tuples
and lists is easy, and 'bag of various things' is a perfectly okay
data structure. In code with type hints, I suspect all of that may
get awkward in the same way this 'logEntryType
' is.
Despite having gone through this exercise, I'm not sure how I feel about using type hints in Python. I suspect that I need to write something more substantial with type hints, or try to retrofit some of our existing code with them, or both, before I can have a really solid view. But at the very least they didn't make me dislike the experience.