Motivation

Software tools are built using functions—reusable blocks that enhance our productivity. Libraries like numpy provide functions that boost productivity. However, the foundation of using functions effectively lies in writing excellent ones that are clear, descriptive, and concise, in that order.

Type checking in Python is a powerful feature that helps achieve these goals by adding type annotations to functions, though its application extends to variables, classes, methods, and more.

Toy examples

Consider a basic function example for beginners:

def print_hello_world(name):
    print(f"Hello, {name}!")

In the above code, the parameter required is name. However, we can make it clear that name should be a str:

def print_hello_world(name: str):
    print(f"Hello, {name}!")

If there is a return value, you can specify its type using -> str:

def return_hello_world(name: str) -> str:
    return f"Hello, {name}!"

Practical examples

Now, let’s look at a more practical example from research code. The following returns a list of random numbers, either as float or int.

def generate_random_numbers(
    count: int, low: int | float, high: int | float, is_float=True
) -> int | float:
    """
    Generate a list of random numbers (floating-point or integer).
    """
    if is_float:
        return [random.uniform(low, high) for _ in range(count)]
    else:
        return [random.randint(int(low), int(high)) for _ in range(count)]

The type information int | float provided in the function specifies explicitly whether it returns a float or an int.

min_dists = generate_random_numbers(100, 2.0, 4.0)  # float
atom_counts = generate_random_numbers(100, 1000, 3000, is_float=False)  # int

Dyanmic vs static type checking

I have written extensive tutorials on Swift, a language that requires knowing the type of each variable in advance. In contrast, Python allows dynamic typing, where types can be assigned later:

my_name = None
my_name = "Bob" # Dynamically typed as 'Str'

In Python, this is called dynamic typing, where the type of my_name is not assigned initially but determined later as str.

In Swift, the type must be known and cannot change after complied, illustrating static type checking:

let my_name = "Bob"  # Static type inference

Benefits of static type checking

Static type checking enhances the development experience by providing autocomplete and function signatures in IDEs, as demonstrated in the generate_random_numbers example above.

Additionally, static type checking may improve performance, potentially improving performance by allowing the compiler to optimize based on known types.

Automate

To minimize time spent on type-related bugs and maximize productivity, automating type checks is crucial. mypy is a tool that can be installed and run to check for type consistency:

We install via:

pip install mypy
mypy folder_path

mypy prints any potential errors found in the code. It is simple and straightforward to use.

Another example

Here is an example function that uses static typing to calculate distances:

def calc_dist_two_cart_points(
    point1: list[float],
    point2: list[float],
) -> float:
    """
    Calculate the Euclidean distance between two points
    in Cartesian coordinates.
    """
    diff = np.array(point2) - np.array(point1)
    distance = float(np.linalg.norm(diff))

    return distance

References

  • https://peps.python.org/pep-0484/
  • https://github.com/python/mypy