Python: Defining distinct types using NewType class

Updated: February 19, 2024 By: Guest Contributor Post a comment

Introduction

In recent versions of Python, particularly Python 3.10 and beyond, the language’s type-hinting capabilities have significantly expanded. Among the many features introduced, the NewType function from the typing module stands out as a powerful tool for creating distinct types. This tutorial will navigate the creation, usage, and implications of using NewType to define distinct types in Python, complemented with practical examples and best practices.

Why Distinct Types? In software development, ensuring that your function parameters, return types, and variable annotations are clearly specified can drastically reduce bugs and misunderstandings about your code’s intent. Python’s dynamic nature makes it permissive and flexible, but this can often lead to confusion and errors in complex systems. By using distinct types, developers can leverage Python’s type checking to enforce stricter type constraints and enhance code readability.

Understanding NewType

The NewType helper function allows developers to create distinct types, which at runtime act exactly like the types they are based on. However, during type checking, these are treated as separate, specific types. This mechanism aids in catching certain classes of bugs early in the development process.

from typing import NewType

UserId = NewType('UserId', int)

# At runtime, UserId acts exactly like an int, but type checkers treat it as a distinct type.

Creating New Types

To create a new type, you need to call the NewType function with two arguments: a string representing the name of the new type and the type from which it should derive.

from typing import NewType

Distance = NewType('Distance', float)

# Now, Distance is treated as a distinct type from float, providing clarity and type safety in your APIs.

Using New Types in Functions

Once you have created a new type, you can use it in function signatures to make your intentions clearer and enforce type safety:

def travel(distance: Distance) -> None:
    assert type(distance) == float  # At runtime, distance is still a float.
    print(f"Traveling {distance} miles")

Type Checking and Dynamic Nature

It’s important to note that while NewType aids in making type hints more explicit, it doesn’t change the runtime behavior of Python. Python remains a dynamically-typed language, and using NewType doesn’t introduce runtime type checks. For static type checking, you’ll need to use tools like mypy.

Advanced Examples

Let’s examine more complex uses of NewType, incorporating it into classes and other typing constructs:

from typing import NewType, List

EmployeeId = NewType('EmployeeId', int)

class Employee:
    def __init__(self, id: EmployeeId):
        self.id = id

# Creating a list of employee IDs, now more explicitly typed.
employee_ids: List[EmployeeId] = [EmployeeId(1), EmployeeId(2)]

Common Pitfalls and How to Avoid Them

While NewType can significantly improve your code’s type safety, there are common pitfalls to be aware of, such as treating new types as entirely new classes and expecting runtime behavior changes. Remember, NewType is primarily a tool for static type checking.

Conclusion

Python’s NewType offers a compelling way to introduce distinct types into your codebase, providing clarity and preventing certain kinds of bugs. Now equipped with the knowledge of how to define and use these distinct types, you can make your Python programs more robust and readable. Remember that NewType is only a part of Python’s type hinting system, and embracing the full spectrum of typing features can considerably improve your development experience.