Beartype: Fast, Efficient Runtime Type Checking for Python

Beartype vs Static Type Checking

Static type checkers like mypy perform type checking only during development/compile time. This means that type errors may not be caught until runtime, potentially causing issues.

Let’s consider the following example:

# typehint.py
def calculate_statistics(data: list[float]) -> dict[str, float]:
    return {
        "mean": sum(data) / len(data),
        "first": data[0]
    }

numbers = [1, "a", 3]  # mypy error, but code will run
result = calculate_statistics(numbers)  # Fails at runtime during sum()

Running mypy on this code will raise an error, but the code will still run.

$ mypy typehint.py
typehint.py:8: error: Argument 1 to "calculate_statistics" has incompatible type "list[object]"; expected "list[float]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

However, running the code will raise a TypeError at runtime.

$ python typehint.py
Traceback (most recent call last):
  File "typehint.py", line 8, in <module>
>   result = calculate_statistics(numbers)  # Fails at runtime during sum()
>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|     numbers              = [1, 'a', 3]
  File "typehint.py", line 3, in calculate_statistics
>   "mean": sum(data) / len(data),
>           ^^^^^^^^^
|     data = [1, 'a', 3]
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Beartype is a runtime type checker that enhances type safety by performing type verification at runtime. This catches issues that static analysis might overlook.

from beartype import beartype

@beartype
def calculate_statistics(data: list[float]) -> dict[str, float]:
    return {
        "mean": sum(data) / len(data),
        "first": data[0]
    }

# Fast runtime checking with clear errors
numbers = [1, "a", 3]
result = calculate_statistics(numbers)
---------------------------------------------------------------------------

BeartypeCallHintParamViolation            Traceback (most recent call last)

Cell In[15], line 12
     10 # Fast runtime checking with clear errors
     11 numbers = [1, "a", 3]
---> 12 result = calculate_statistics(numbers)

BeartypeCallHintParamViolation: Function calculate_statistics() parameter data=[1, 'a', 3] violates type hint list[float], as list index 0 item int 1 not instance of float.

Beartype vs Pydantic

Pydantic is another popular library for runtime type checking. However, it adds overhead during model creation and validation.

from pydantic import BaseModel
from typing import List

class StatisticsInput(BaseModel):
    data: List[float]

class StatisticsOutput(BaseModel):
    mean: float
    first: float

def calculate_statistics(input_data: StatisticsInput) -> StatisticsOutput:
    return StatisticsOutput(
        mean=sum(input_data.data) / len(input_data.data),
        first=input_data.data[0]
    )

# Validates during model creation, but adds overhead
numbers = [1, "a", 3]
input_model = StatisticsInput(data=numbers)
---------------------------------------------------------------------------

ValidationError                           Traceback (most recent call last)

Cell In[14], line 19
     17 # Validates during model creation, but adds overhead
     18 numbers = [1, "a", 3]
---> 19 input_model = StatisticsInput(data=numbers)

ValidationError: 1 validation error for StatisticsInput
data.1
  Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='a', input_type=str]

Beartype offers efficient runtime type checking with constant time complexity. Its dynamic wrappers around functions and methods enable flexible and efficient type-checking, making it a great choice for ensuring type safety in your code.

Link to Beartype.

Related Posts

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top

Work with Khuyen Tran

Work with Khuyen Tran