Increasing namedtuple Readability with Type Hints
Posted by Aly Sivji in Quick Hits
Dynamic typing refers to programs checking variable types at runtime versus compile time. By not having to define the variable type as we write code, we can work faster and take advantage of duck typing.
On the other hand, we do miss out on some benefits that types bring. Similiar to how the principles of test-driven development force us to write smaller, more testable functions, thinking about types improves software design by making us cognizant of the values our variables hold as well as the values our functions can return.
Fortunately for Python programmers, PEP 484 added optional type hints to the language. Type hints will not be enforced by the interpreter, but rather used by static code analyzers to enable features such as linting and code introspection.
Optional type hints allow us to use all the dynamic capabilities of Python with the ability to be as formal as the situation requires.
Over the past few weeks I've been working my way through the Advent of Code problem set. Having a lot of fun using parts of the Python Standard Library I never get to use at work.
This includes the typing
module. I only started using it a couple of weeks ago, but it has definitely improved how I approach problem solving.
Instead of blindly attacking each puzzle with a "God Method", I break the problem down into components that I can piece together into a working program.
Type hints allow me to express my thinking more concretely. This is best demonstrated with an example:
Brain: This function should take a string, parse it with some regex, and return a list
from typings import List
def parse_instructions(lines: str) -> List[Instruction]:
instructions = []
for line in lines.splitlines():
parts = INSTRUCTION_REGEX.search(line)
(register, op, amount, conditional_register,
conditional_op, conditional_amount) = parts.groups()
result = Instruction(register,
'+' if op == 'inc' else '-',
int(amount),
conditional_register,
conditional_op,
int(conditional_amount))
instructions.append(result)
return instructions
With Type Hints, we can explicitly see the function takes in a string
and returns a list
of Instruction
objects.
Sure this information is also in the function and we can figure out what it does as we read each line, but self-documenting code increases readability with minimal work.
One of my favourite features of the typings
module is typings.NamedTuple
, a typed version of the collections.namedtuple
container type.
In this post we will explore how we can use the typed form of namedtuples to make code more readable. As code is read a lot more than it is written, every little bit of improvement counts!
# "regular" namedtuples
from collections import namedtuple
# let's create an Employee object
Employee = namedtuple('Employee',
'name age title department job_description')
bob = Employee('Bob Smith',
35,
'Senior Software Engineer',
'Technology',
['mentor junior developers',
'fix production issues',
'understand deployment pipeline'])
bob
attribute_to_examine = bob.name
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = bob.age
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = bob.title
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = bob.department
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = bob.job_description
print(attribute_to_examine)
print(type(attribute_to_examine))
namedtuples are a great language feature as they allow our code to be more explicit about which field(s) we are working with.
print(bob[0])
## versus
print(bob.name)
Using the typing
module, we can be even more explicit about our data structures.
from typing import List, NamedTuple
class EmployeeImproved(NamedTuple):
name: str
age: int
title: str
department: str
job_description: List
emma = EmployeeImproved('Emma Johnson',
28,
'Front-end Developer',
'Technology',
['build React components',
'test front-end using Selenium',
'mentor junior developers'])
emma
attribute_to_examine = emma.name
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = emma.age
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = emma.title
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = emma.department
print(attribute_to_examine)
print(type(attribute_to_examine))
attribute_to_examine = emma.job_description
print(attribute_to_examine)
print(type(attribute_to_examine))
emma[0]
Personally I find the typed version of Employee
to be an improvement over the untyped version.
I'm not saying we should all start using types or run our code thru Mypy, but it's good to be aware of language features that can help reduce complexity and increase readability.
Comments