Optimizing Python Type Checking for Library Maintainers

Prioritize Test Suite Type-Checking Over Source Code

Library maintainers should prioritize running as many type-checkers as possible on their test suites while running only one preferred checker on their internal source code. This approach ensures that the public API remains compatible and usable for the widest range of users, regardless of which type-checker those users employ.

When type-checkers are run on internal source code, they primarily validate internal logic. However, users of a library do not interact with internal implementation details; they interact with the public API. By validating the test suite—which exercises the public API—against multiple checkers, maintainers can guarantee a consistent developer experience for their users without compromising the cleanliness of their own codebase.

The Cost of Multi-Checker Source Validation

Attempting to satisfy multiple type-checkers within a single source file often leads to significant code pollution through redundant type-ignore comments. Because different checkers (such as mypy, Pyrefly, Pyright, ty, and Zuban) have varying levels of strictness and different interpretations of the Python typing spec, a single function may require multiple distinct ignore directives to pass all of them.

For example, in the Polars library, the DataType.__eq__ method requires overloads to handle different return types. To satisfy mypy, Pyrefly, ty, and Pyright simultaneously, the implementation requires four different type-ignore comments within just seven lines of code:

@overload  # type: ignore[override]
def __eq__(  # pyrefly: ignore[bad-override]
    self, other: pl.DataTypeExpr
) -> pl.Expr: ...

@overload
def __eq__(self, other: PolarsDataType) -> bool: ...

def __eq__(self, other: pl.DataTypeExpr | PolarsDataType) -> pl.Expr | bool:  # ty: ignore[invalid-method-override]  # pyright: ignore[reportIncompatibleMethodOverride]

Conversely, running these same checkers against the tests that exercise this functionality typically yields no errors, as the checkers generally agree on the expected behavior of the public API even when they disagree on the internal implementation requirements.

Understanding the Python Type-Checker Landscape

Python's typing ecosystem consists of several prominent tools, including mypy, Pyrefly, Pyright, ty, and Zuban. These tools vary because the official Python typing spec contains ambiguities, particularly regarding how to handle under-specified typing information. This leads to two primary design philosophies:

  1. Strict Checkers: These tools prioritize guarding against potential bugs and may emit false positives to ensure maximum safety. Pyrefly is positioned as a strict, fast, and conformant option.
  2. Lenient Checkers: These tools allow for more gradual adoption of type hints and are less likely to flag ambiguous but potentially valid code.

Community Perspectives and Challenges

Technical discussions among developers highlight several systemic frustrations with the current state of Python typing:

  • Fragmented Ecosystem: Some developers view the need for multiple checkers as evidence that Python's typing feels "tacked on" compared to natively statically typed languages.
  • Performance and Inference: There are complaints regarding the speed of legacy tools like mypy and the requirement to explicitly write types in scenarios where they could be logically inferred.
  • Tooling Overlap: Users report that different tools catch different classes of errors; for instance, some find Pyright stricter on overloads while mypy is more effective at catching None issues.
  • AI Integration: Some argue that statically typed languages are superior for AI-assisted coding because they provide the determinism necessary to anchor probabilistic coding agents.

"The whole type checking experience in python has disappointed me deeply... the language is quickly running blindly to the worst of all worlds in regards to typing."

Summary of Recommended Strategy

To balance code quality with maintenance overhead, library maintainers should adopt the following hierarchy:

  • Source Code: Use one type-checker (e.g., Pyrefly for strictness and speed) to maintain internal consistency.
  • Test Suite: Integrate multiple type-checkers (mypy, Pyright, ty, etc.) into the CI pipeline to validate the public API's compatibility across the ecosystem.

Sources