Siv Scripts

Solving Problems Using Code

Mon 01 January 2018

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!

In [1]:
# "regular" namedtuples
from collections import namedtuple
In [2]:
# let's create an Employee object
Employee = namedtuple('Employee',
                      'name age title department job_description')
In [3]:
bob = Employee('Bob Smith', 
               35, 
               'Senior Software Engineer', 
               'Technology',
               ['mentor junior developers',
                'fix production issues',
                'understand deployment pipeline'])
In [4]:
bob
Out[4]:
Employee(name='Bob Smith', age=35, title='Senior Software Engineer', department='Technology', job_description=['mentor junior developers', 'fix production issues', 'understand deployment pipeline'])
In [5]:
attribute_to_examine = bob.name
print(attribute_to_examine)
print(type(attribute_to_examine))
Bob Smith
<class 'str'>
In [6]:
attribute_to_examine = bob.age
print(attribute_to_examine)
print(type(attribute_to_examine))
35
<class 'int'>
In [7]:
attribute_to_examine = bob.title
print(attribute_to_examine)
print(type(attribute_to_examine))
Senior Software Engineer
<class 'str'>
In [8]:
attribute_to_examine = bob.department
print(attribute_to_examine)
print(type(attribute_to_examine))
Technology
<class 'str'>
In [9]:
attribute_to_examine = bob.job_description
print(attribute_to_examine)
print(type(attribute_to_examine))
['mentor junior developers', 'fix production issues', 'understand deployment pipeline']
<class 'list'>

namedtuples are a great language feature as they allow our code to be more explicit about which field(s) we are working with.

In [10]:
print(bob[0])

## versus

print(bob.name)
Bob Smith
Bob Smith

Using the typing module, we can be even more explicit about our data structures.

In [11]:
from typing import List, NamedTuple
In [12]:
class EmployeeImproved(NamedTuple):
    name: str
    age: int
    title: str
    department: str
    job_description: List
In [13]:
emma = EmployeeImproved('Emma Johnson',
                        28,
                        'Front-end Developer',
                        'Technology',
                        ['build React components',
                         'test front-end using Selenium',
                         'mentor junior developers'])
In [14]:
emma
Out[14]:
EmployeeImproved(name='Emma Johnson', age=28, title='Front-end Developer', department='Technology', job_description=['build React components', 'test front-end using Selenium', 'mentor junior developers'])
In [15]:
attribute_to_examine = emma.name
print(attribute_to_examine)
print(type(attribute_to_examine))
Emma Johnson
<class 'str'>
In [16]:
attribute_to_examine = emma.age
print(attribute_to_examine)
print(type(attribute_to_examine))
28
<class 'int'>
In [17]:
attribute_to_examine = emma.title
print(attribute_to_examine)
print(type(attribute_to_examine))
Front-end Developer
<class 'str'>
In [18]:
attribute_to_examine = emma.department
print(attribute_to_examine)
print(type(attribute_to_examine))
Technology
<class 'str'>
In [19]:
attribute_to_examine = emma.job_description
print(attribute_to_examine)
print(type(attribute_to_examine))
['build React components', 'test front-end using Selenium', 'mentor junior developers']
<class 'list'>
In [20]:
emma[0]
Out[20]:
'Emma Johnson'

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