Why I use Type Hinting in Python and Why You Should Too

Published

I love Python. It’s my first language and it’s great: it’s easy to use because of dynamic typing, the syntax is clear and light and there are a lot of packages to use. Python really is a great tool to have!

Still, for all its positives, code written in Python tends to be extremely messy since the language gives a lot of leeway to the developper. This is fine for small scripts, but quickly becomes overwhelming in bigger projects.

Introducing PEP484 and the Typing Package

A great way to make your code clearer is to introduce some typing. Typing means to specify the type used by a given variable.

While Python will remain dynamically typed, the Python Enhancement Proposal 484 (or PEP 484) introduced the possibility of specifying static type checking in your code. This means you can specify expected types in function calls, or variable declarations, and the the interpreter or your IDE will warn you when using incorrect variables in function calls.

def PrintFunction(textInput: str) -> None:
    print(textinput)

More complex types (collection types mostly), will require import specific declarations from the typing package.

from typing import List

def AddFunction(listValues: List[float]) -> float:
    return sum([4, 5, 7])

A Special Case: Methods Referencing Their Class

There is one case where typing will throw an error: when you are creating a class and you specify the output type of a method as being the class being created.

See below, this will throw an error:

class Foo(object):
    def __init__(self):
        pass
    
    def Bar(self) -> Foo:
        return Foo()

Thankfully, the fix is simple:

  • Either don’t use typing for that case
  • Or import the annotations module

Importing the annotations module will fix this error as it turns all type hinting as strings to be ignored by the interpreter. That means the Interpreter will not warn you about incorrect variable types being used, but your IDE should still do it so I think it’s an acceptable tradeoff.

from __futures__ import annotations 

class Foo(object):
    def __init__(self):
        pass
    
    def Bar(self) -> Foo:
        return Foo()

Conclusion

This additional typing possibility is a great help for complex python projects, by making your code clearer with less documentation.

I’ve been using Static Typed languages much more in the past few years, and one thing I have realized is that the additional constraint of specifying types actually helps ensure your codebase remains manageable.

Finally it’s also a great way to keep your functions simple: if a function ends up with a return type akin to ‘List[Union[float, bool], int, Dict[str, str]]’, you really should do some refactoring.