Wandering Thoughts archives

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.

python/TypeHintsBriefBrush written at 22:07:12;


Page tools: See As Normal.
Search:
Login: Password:

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.