Tutorials  /  Python

Python typing module - Use type checkers effectively

Ccentron Redaktion · February 2024 ·6 min read ·Python, Tutorial

Python’s typing module attempts to provide a way of hinting types to help static type checkers and linters accurately predict errors.

Due to Python having to determine the type of objects during run-time, it sometimes gets very hard for developers to find out what exactly is going on in the code.

Even external type checkers like PyCharm IDE do not produce the best results; on average only predicting errors correctly about 50% of the time, according to this answer on StackOverflow.

Python attempts to mitigate this problem by introducing what is known as type hinting (type annotation) to help external type checkers identify any errors. This is a good way for the programmer to hint the type of the object(s) being used, during compilation time itself and ensure that the type checkers work correctly.

This makes Python code much more readable and robust as well, to other readers!

NOTE: This does not do actual type checking at compile time. If the actual object returned was not of the same type as hinted, there will be no compilation error. This is why we use external type checkers, such as mypy to identify any type errors.

For using the typing module effectively, it is recommended that you use an external type checker/linter to check for static type matching. One of the most widely used type checkers in use for Python is mypy, so I recommend that you install it before reading the rest of the article.

We will be using mypy as the static type checker in this article, which can be installed by:

Code
pip3 install mypy

You can run mypy to any Python file to check if the types match. This is as if you are ‘compiling’ Python code.

Code
mypy program.py

After debugging errors, you can run the program normally using:

Console
python program.py

Now that we have our prerequisites covered, let’s try to use some of the module’s features.

VM

Matching infrastructure at centron

Scripts and services eventually need an environment that keeps running: ccloud³ VMs from €3.12 per month, billed by the hour. Rent a cloud server →

Type Hints / Type Annotations

On functions

We can annotate a function to specify its return type and the types of its parameters.

Python
def print_list(a: list) -> None:
    print(a)

This informs the type checker (mypy in my case) that we have a function print_list(), that will take a list as an argument and return None.

Python
def print_list(a: list) -> None:
    print(a)

print_list([1, 2, 3])
print_list(1)

Let’s run this on our type checker mypy first:

Code
vijay@JournalDev:~ $ mypy printlist.py 
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)

As expected, we get an error; since the line #5 has the argument as an int, rather than a list.

On Variables

Since Python 3.6, we can also annotate the types of variables, mentioning the type. But this is not compulsory if you want the type of a variable to change before the function returns.

Console
# Annotates 'radius' to be a float
radius: float = 1.5

# We can annotate a variable without assigning a value!
sample: int

# Annotates 'area' to return a float
def area(r: float) -> float:
    return 3.1415 * r * r


print(area(radius))

# Print all annotations of the function using
# the '__annotations__' dictionary
print('Dictionary of Annotations for area():', area.__annotations__)

Output of mypy:

Code
vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}

Python typing module - Type Aliases

The typing module provides us with Type Aliases, which is defined by assigning a type to the alias.

Python
from typing import List

# Vector is a list of float values
Vector = List[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)

Output

Code
vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]

In the above snippet, Vector is an alias, which stands for a list of floating point values. We can type hint at an alias, which is what the above program is doing.

The complete list of acceptable aliases is given here.

Let’s look at one more example, which checks every key:value pair in a dictionary and check if they match the name:email format.

Python
from typing import Dict
import re

# Create an alias called 'ContactDict'
ContactDict = Dict[str, str]

def check_if_valid(contacts: ContactDict) -> bool:
    for name, email in contacts.items():
        # Check if name and email are strings
        if (not isinstance(name, str)) or (not isinstance(email, str)):
            return False
        # Check for email xxx@yyy.zzz
        if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
            return False
    return True


print(check_if_valid({'vijay': 'vijay@sample.com'}))
print(check_if_valid({'vijay': 'vijay@sample.com', 123: 'wrong@name.com'}))

Output from mypy

Code
vijay@JournalDev:~ $ mypy validcontacts.py 
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)

Create user defined datatypes using NewType()

We can use the NewType() function to create new user defined types.

Python
from typing import NewType

# Create a new user type called 'StudentID' that consists of
# an integer
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)

The static type checker will treat the new type as if it were a subclass of the original type. This is useful in helping catch logical errors.

Python
from typing import NewType

# Create a new user type called 'StudentID'
StudentID = NewType('StudentID', int)

def get_student_name(stud_id: StudentID) -> str:
    return str(input(f'Enter username for ID #{stud_id}:\n'))

stud_a = get_student_name(StudentID(100))
print(stud_a)

# This is incorrect!!
stud_b = get_student_name(-1)
print(stud_b)

Output from mypy

Code
vijay@JournalDev:~ $ mypy studentnames.py  
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)

The Any type

This is a special type, informing the static type checker (mypy in my case) that every type is compatible with this keyword.

Consider our old print_list() function, now accepting arguments of any type.

Python
from typing import Any

def print_list(a: Any) -> None:
    print(a)

print_list([1, 2, 3])
print_list(1)

Now, there will be no errors when we run mypy.

Code
vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1

All functions without a return type or parameter types will implicitly default to using Any.

Python
def foo(bar):
    return bar

# A static type checker will treat the above
# as having the same signature as:
def foo(bar: Any) -> Any:
    return bar

You can thus use Any to mix up statically and dynamically typed code.

Conclusion

In this article, we have learned about the Python typing module, which is very useful in the context of type checking, allowing external type checkers like mypy to accurately report any errors.

This provides us with a way to write statically typed code in Python, which is a dynamically typed language by design!

Jetzt 200 € Guthaben sichern

Testen Sie Ihr Setup auf ccloud³

Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.

centron Redaktion Technische Redaktion

Das Redaktionsteam von centron schreibt Anleitungen aus dem Betriebsalltag: getestet auf unserer eigenen Plattform, betrieben im Rechenzentrum in Hallstadt bei Bamberg.

Kategorie Python
Teilen
Noch offene Fragen?

Our team will help you with your specific setup - in German or English, by people who run the platform themselves.

War dieses Tutorial hilfreich?

Your answer is stored anonymously and helps us improve our tutorials.

Kommentare

No comments yet - be the first to ask a question about this tutorial.

Sign in to comment

Comments are open to centron customers. Sign in to your account to ask a question about this tutorial.

Weiterlesen

Das könnte Sie auch interessieren

Jetzt kostenlos anfangen

Melden Sie sich an und erhalten Sie in den ersten 60 Tagen ein Guthaben von 200 € bei centron.

Dieses Werbeangebot gilt nur für neue Konten. Angebot ausschließlich für Gewerbetreibende.

Jetzt loslegen Sales kontaktieren