Static typing with Python#

When: Friday 26 April
Presenters: Pierre Chanial, Senwen Den, Alexandre Boucaud

Type Hints with Code Examples#

Static typing in Python has been enhanced over time through the introduction of type hints. These hints allow developers to specify the expected types of variables, function parameters, and return values. While Python remains dynamically typed, these hints offer several benefits:

Benefits of Type Hints#

  • Improved Readability: Type hints make the code easier to understand by clearly indicating what types of values are expected or returned.

  • Better Tooling Support: Modern IDEs and linters can leverage type hints to provide better autocompletion, error checking, and refactoring capabilities.

  • Documentation Enhancement: Type hints serve as inline documentation, making the code self-documenting to a certain extent.

Example Usage#

from typing import List

def greet_all(names: List[str]) -> None:
    """Print greetings for all names."""
    for name in names:
        print(f"Hello, {name}!")

# Using the function
greet_all(["Alice", "Bob"])

In this example, List[str] indicates that the names parameter should be a list of strings. The -> None part specifies that the function does not return any value.

Static Type Checkers#

Python’s ecosystem includes tools like mypy, pyright, and pytype that perform static type checking based on the type hints provided in the code. These tools analyze the code without executing it, identifying potential type-related errors such as incorrect argument types or unexpected return types.

Example: Using Mypy#

First, install mypy:

pip install mypy

Then, run it on your Python file:

mypy my_script.py

Mypy will report any type inconsistencies found according to the type hints in your script.

Example output#

From the example typing example above, one can create a call that does not match the function signature in bad_usage.py

bad_usage.py
from typing import List

def greet_all(names: List[str]) -> None:
    for name in names:
        print(f"Hello, {name}!")

greet_all([1, 2])

While this is a perfectly valid Python script,

$ python bad_usage.py
Hello, 1!
Hello, 2!

mypy will report an error

$ mypy bad_usage.py
bad_usage.py:7: error: List item 0 has incompatible type "int"; expected "str"  [list-item]
bad_usage.py:7: error: List item 1 has incompatible type "int"; expected "str"  [list-item]
Found 2 errors in 1 file (checked 1 source file)

Interest for Scientific Developers#

Static typing, facilitated by type hints and checked by static type checkers, offers compelling advantages for scientific developers:

Improved Code Quality#

  • Reduced Bugs: By catching type-related errors early, static typing helps reduce runtime bugs, especially those related to incorrect data types.

  • Enhanced Debugging: When issues arise, having clear type expectations makes debugging more straightforward.

Collaboration and Documentation#

  • Clearer Communication: Sharing code with explicit type annotations makes it easier for others (and future you) to understand the intended usage and structure of complex functions and classes.

  • Self-documenting Code: Type hints act as a form of documentation, reducing the need for separate docstrings to explain the types of inputs and outputs.

Scalability and Maintenance#

  • Easier Refactoring: As projects grow, static typing supports safer refactoring efforts by preventing unintended changes to data types.

  • Long-term Maintainability: Projects adopting static typing from the start are likely to maintain higher code quality standards, benefiting long-term maintenance and evolution.

In summary, while Python’s dynamic nature is one of its strengths, embracing static typing through type hints and static analysis tools can significantly enhance code quality, readability, and maintainability, particularly beneficial for scientific development where precision and reproducibility are paramount.

Scientific example#

An example illustrating how static typing helped catch a bug in a scientific project involves a scenario where a developer was working on a complex mathematical computation library in Python. Without static typing, the developer might have encountered runtime errors due to incorrect data types being passed to functions, leading to inaccurate computations or even crashes. However, by incorporating type hints and utilizing a static type checker like mypy, the developer was able to identify and rectify type-related issues early in the development process.

Imagine a function within the library that calculates the square root of a number. Without type hints, calling this function with an integer argument instead of a float could lead to a runtime error or incorrect results due to implicit type conversion.

Before Static Typing#

import math

def sqrt(number):
    return math.sqrt(number)

Calling sqrt(10) would work fine since math.sqrt accepts integers, but it would implicitly convert the integer to a float. However, if the caller mistakenly passes an integer expecting a float return type, it could lead to confusion or bugs elsewhere in the code.

After Adding Type Hints#

from typing import Union
import math

def sqrt(number: Union[int, float]) -> float:
    return math.sqrt(number)

By specifying that number can be either an int or a float, and that the function returns a float, the type checker can now warn the developer if they attempt to pass an incompatible type. This prevents potential bugs and ensures that the function behaves as expected across different data types.

This example demonstrates how static typing, through type hints and static type checking, can catch bugs related to incorrect data types early in the development process. It ensures that functions receive the correct types of arguments, leading to more reliable and accurate scientific computations. This approach reduces the likelihood of runtime errors and enhances the overall quality and maintainability of the code, which is crucial in scientific projects where precision and correctness are paramount.