Type Checking In Python: Catching Bugs Before They Bite
Type checking in Python enhances code clarity and reliability. We catch errors early and maintain high software quality by using type hints and one or more type checkers. This article covers the benefits of type hints, and how to install and run mypy, pyright, and pyre.
Overview
In Python, dynamic typing allows for great flexibility but can lead to subtle and hard-to-find bugs. This is because Python's type checks are limited and happen at runtime.
What is Type Checking?
Type checking is verifying the types of variables and expressions in your code. This happens at compile time in statically typed; in dynamically r programs, they are not type-checked before running.
The runtime might make some type assertions, but our code needs to deal with inputs having unexpected types; otherwise, our code might fail in surprising ways.
Although we shouldn't expect Python to achieve the rigor of Static type-checking of languages like Scala or Haskell, the languages have slowly gained type-checking capabilities Since Python 3.5 (PEP 484).
Python is interpreted, and Type-checking would be too costly if implemented directly. Since the type-checking behavior is defined but not implemented in the interpreter, different Type Checker implementations exist, each with pros and cons.
Installing Type Checkers: mypy
, pyright
, and pyre
Adding types to our Python programs is called Type Hinting. Because adding type information is optional and the interpreter does not use it, we must use a type-checking tool to reap the benefits of adding types to our programs.
The Python team's default implementation is mypy
. Another notable implementation is Microsoft’s pyright
, which has excellent Visual Studio Code integration. Finally, Meta created its tool called pyre
, which is optimized to manage their big code bases.
The Reference Type Checker mypy
mypy
is the most widely used static type checker for Python, it`s developed by the Python team.
To install mypy
, use pip, Python's package manager:
pip install mypy
Once installed, you can run mypy
on your Python files or directories:
mypy some_python_script.py
This command will analyze your code and report any type errors, helping us catch issues before they become runtime problems.
Type Checking while Typing With Microsoft pyright
pyright
is a fast type-checker developed by Microsoft. It's trendy in the Visual Studio Code ecosystem and can be easily installed as a Visual Studio extension:
But it can also be used as a standalone tool:
npm install -g pyright
pyright some_python_script.py
Type Checking Big Code Bases With pyre
pyre
is a fast, scalable type checker built by Facebook. It's designed to handle large codebases efficiently and integrates well with modern Python development practices.
As with mypy
we install it using pip
:
pip install pyre-check
pyre
requires a setup before we use it. It will create a .pyre_configuration
file in your project, which you can customize according to our needs:
pyre init
The primary use case for pyre
is running it on our complete program instead of type-checking file by file:
pyre
Adding Type Hints to Our Python Code
We must add Type Hints to our code to benefit from Type Checkers. As a bonus, they also improve our code's readability by clarifying some of our code constraints.
Simple Type Hints
Let's start by looking at a simple add function and how can it be called with the wrong types:
def average(a, b):
return (a + b) / 2
result_avg = average(12, 5)
print(result_avg + 2)
result_avg = average(10, "5")
# TypeError: unsupported operand type(s) for +: 'int' and 'str'
Running a mypy
or any other Type Checker won't bring us any benefit since the code above has no type information for it to make any judgments about our code.
Starting with Python 3.5, we can annotate variables, function arguments, and function return values. Functions and arguments are annotated by adding a colon after the name and writing a type after the colon:
def average(a: float, b: float):
return (a + b) / 2
result_avg = average(12, 5)
print(result_avg + 2)
result_avg = average(10, "5")
# TypeError: unsupported operand type(s) for +: 'int' and 'str'
If we just run the code above with Python, we’ll get the same Type Error as before, but now we have the chance to run a Type Checker, which will tell us our code is incorrect, where it is erroneous, and why:
This is an improvement, but we still leave some ambiguity in our code because we still need to add type hint for the returned value. Given Python already uses the colon to terminate function declarations, a new symbol (->
) was added for writing type hints on return types:
def average(a: float, b: float) -> float:
return (a + b) / 2
result_avg = average(12, 5)
print(result_avg + 2)
On the right side of the colon, we can use built-in types/classes, abstract base classes, user-defined classes, and types from the typing module.
Collection Type Hints
That was an improvement, but what if our function needs to calculate the average over a collection of elements instead of only two? If we add list
as a type hint, it will prevent us from passing non-list objects to the function, but we would still be able to give a non-numeric object as part of that list:
def list_average(nums: list):
return sum(nums) / len(nums)
in_nums = [2, "3", 5, 20]
avg = list_average(in_nums)
print(avg + 2)
We need a way to add type information about the elements of collection classes. In statically typed languages, this is known as Generics, and support for generic typing was included in 3.5 (PEP 484) and improved in 3.7 (PEP 560), 3.11 (PEP 646), and 3.12 (PEP 695).
We will discuss Generics in depth in a future article. For now, we’ll limit ourselve to the simplest case of adding a type hint to the element of a standard Python collection. For this, we need to import the collection abstract class from the typing module and add the type of the elements between square brackets after the collection type:
from typing import List
def list_average(nums: List[float]) -> float:
return sum(nums) / len(nums)
in_nums = [2, "3", 5, 20]
avg = list_average(in_nums)
print(avg + 2)
Now, we can catch the error while type checking:
Conclusion
Type hinting in Python is a powerful tool for enhancing code quality, readability, and maintainability.
By leveraging type hints and tools like mypy
, pyright
, and/or pyre
, we can catch typing errors early in the development process, preventing them from becoming runtime issues. Additionally, it facilitates collaboration by communicating a contract for our functions and data structures.
Incorporating type-checking into our development workflow doesn’t require a complete codebase overhaul. We can start small, gradually adding type hints to critical functions.
In our next article, we will see how to integrate Python’s type checking into a continuous integration workflow with GitHub Actions. We`ll use it to keep one or more branches type-safe, to the extent of the type hinting we add, with minimal or no manual intervention.
Ultimately, adopting type checking helps us improve as developers and help us learn concepts transferable to other programming languages like Haskell, or Scala.
Addendum: A Special Note for Our Readers
I decided to delay the introduction of subscriptions. You can read the full story here.
If you find our content helpful, there are several ways you can support us:
- The easiest way is to share our articles and links page on social media; it is free and helps us greatly.
- If you want a great experience during the Chinese New Year, I am renting my timeshare in Phuket. A five-night stay in this resort in Phuket costs 11,582 € on Expedia. I am offering it in USD at an over 40% discount compared to that price. I received the Year of the Snake in style.
- If your finances permit it, we are happy over any received donation. It helps us offset the site's running costs and an unexpected tax bill. Any amount is greatly appreciated:
- Finally, some articles have links to relevant goods and services; buying through them will not cost you more. And if you like programming swag, please visit the TuringTacoTales Store on Redbubble. Take a look. Maybe you can find something you like: