beartype review
beartype turns Python annotations into checks that run when your code runs. Put @beartype on a callable, enable a package through beartype.claw, or use beartype.door to test an arbitrary object against a hint. Its default container strategy samples an item instead of walking the whole collection, which keeps checks cheap but cannot prove that every member is valid. Version 0.22.9 adds official PyInstaller handling and limited compatibility work for Astral's ty checker. Our Python 3.12 import worked, and the distribution includes py.typed metadata.
beartype 0.22.9 installed in 0.2 seconds as a single 6 MB package in our sandbox, imported in 0.45 seconds, and produced 0 audit findings. It is a useful runtime tripwire for annotated Python, but its default container strategy samples an item rather than checking every member.
We installed it
| Install | ✓ · 0.2s | 1 package on disk · 6 MB |
| Import | ✓ | import beartype in 0.45s · pure Python · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does beartype install cleanly?
Yes. In a fresh container with an empty cache, pip install beartype finished in 0.2s, leaving 1 package and 6 MB on disk. pip-audit reported no known vulnerabilities.
What does beartype need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import beartype succeeded in 0.45s, and the package ships py.typed for type checkers.
beartype or typeguard: which should you use?
typeguard: Choose it when exhaustive collection checking matters more than constant per-call work. beartype 0.22.9 installed in 0.2 seconds as a single 6 MB package in our sandbox, imported in 0.45 seconds, and produced 0 audit findings.
When should you not use beartype?
Every collection element must be checked. The documented default samples one item, while the Ologn and On strategies remain unimplemented in the strategy enum
Use it if
- Your functions already have useful annotations and bad runtime values are reaching them from plugins, files, or network boundaries
- You want to enable runtime checking across one package through an import hook instead of adding a decorator to every callable
- You need isinstance-style questions for parameterized hints such as list[dict[str, int]], which built-in isinstance cannot accept
- A sampled check is preferable to scanning every item in a large collection on every call
- Every collection element must be checked. The documented default samples one item, while the Ologn and On strategies remain unimplemented in the strategy enum
- You need parsing or coercion. A string containing '5' stays a string and fails an int hint; Pydantic is a better fit for building models from external input
- You need errors on code paths that tests never execute. beartype runs with the program, so mypy, pyright, or ty still has a separate job
- Your service is pinned below Python 3.10, which the current package metadata excludes
- Import order is hard to control, as claw hooks affect later imports and do not retrofit modules already present in sys.modules
Setup reality
Our clean Python 3.12 install of beartype 0.22.9 finished in 0.2 seconds. The environment contained one package using 6 MB, and pip-audit reported no known vulnerabilities. The measurement recorded 99 direct dependencies in package metadata. The distribution is pure Python, requires Python 3.10 or newer, includes py.typed, and uses the MIT License. import beartype succeeded in 0.45 seconds.
The decorator needs no credentials or config file. Package-wide checking does have an ordering requirement: call beartype_this_package() near the top of your package init.py, before importing the modules you expect it to cover. A module already loaded in sys.modules is outside that hook. This matters in notebooks, test runners, and plugin hosts that import application code early.
The normal O1 strategy keeps call cost bounded by testing a sampled member of a container. It can therefore miss one wrong item inside a mostly valid list. Use the result as a tripwire for common shape mistakes, not as proof that an entire payload was inspected. Custom Annotated validators also execute at runtime, so expensive regex compilation, I/O, or database work does not belong inside their predicate.
Turning on beartype_all() can expose bad annotations in dependencies you do not own. The README's safer split is to raise for your package and configure third-party violations as warnings. Postponed or forward annotations must still resolve to names available at runtime. Version 0.22.9 handles PyInstaller explicitly; its ty support is described by the release itself as superficial, so keep static-checker changes under test.
Patterns
Reject a wrong argument at the function boundary check-callable
from beartype import beartype
@beartype
def resize(width: int, height: int) -> tuple[int, int]:
return width, height
resize(640, 480)Only annotated parameters and returns are checked. A wrong argument raises a BeartypeCallHintParamViolation.
Enable checks for modules imported later check-package
# acme/__init__.py
from beartype.claw import beartype_this_package
beartype_this_package()
from acme import apiPlace the hook before package imports. It does not revisit modules that Python has already loaded.
Ask whether an object matches a parameterized hint inspect-object
from beartype.door import is_bearable
payload = [{'count': 2}]
if not is_bearable(payload, list[dict[str, int]]):
raise ValueError('bad payload')Container checks use the configured beartype strategy. The default may sample rather than inspect every item.
Raise when an object misses its hint assert-object
from beartype.door import die_if_unbearable
die_if_unbearable({'port': 5432}, dict[str, int])This raises BeartypeDoorHintViolation and is useful outside a decorated function signature.
Require a port inside a valid range constrain-annotated-value
from typing import Annotated
from beartype import beartype
from beartype.vale import Is
Port = Annotated[int, Is[lambda value: 1 <= value <= 65535]]
@beartype
def listen(port: Port) -> None:
passThe predicate runs during the call. Keep it deterministic and cheap.
Map violations to an application exception change-violation-type
from beartype import BeartypeConf, beartype
class InputError(ValueError):
pass
@beartype(conf=BeartypeConf(violation_param_type=InputError))
def ingest(rows: list[str]) -> None:
passParameter and return exception classes can be configured separately.
Warn when code outside your package violates a hint warn-on-dependencies
from beartype import BeartypeConf
from beartype.claw import beartype_all
beartype_all(conf=BeartypeConf(violation_type=UserWarning))A global hook can be noisy. Use warning filters for known dependency problems and keep your own package policy separate.
Make a decorator that performs no checks disable-checks
from beartype import BeartypeConf, BeartypeStrategy, beartype
unchecked = beartype(conf=BeartypeConf(strategy=BeartypeStrategy.O0))
@unchecked
def compute(values: list[float]) -> float:
return sum(values)O0 reduces decoration to the identity path, which is useful when production policy disables runtime checks.
Separate caller mistakes from bad return values catch-call-error
from beartype.roar import (
BeartypeCallHintParamViolation,
BeartypeCallHintReturnViolation,
)
try:
result = build_report(request)
except BeartypeCallHintParamViolation as error:
return {'error': str(error)}, 400
except BeartypeCallHintReturnViolation:
raiseA return violation points to the decorated implementation, while a parameter violation normally belongs at the caller boundary.
Check imports made inside one context scope-import-hook
from beartype.claw import beartyping
with beartyping():
import vendor_pluginThe context affects imports performed inside it. Importing vendor_plugin earlier prevents the hook from seeing it.
Compare the breadth of two hints compare-type-hints
from beartype.door import is_subhint
assert is_subhint(list[int], list[object])
assert not is_subhint(int | str, int)This is useful in registries that rank handlers by how specific their accepted hint is.
Print generated checking code debug-generated-wrapper
from beartype import BeartypeConf, beartype
@beartype(conf=BeartypeConf(is_debug=True))
def normalize(names: list[str]) -> list[str]:
return [name.strip() for name in names]Debug mode exposes the generated wrapper, which helps explain an unexpected check during tests.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| typeguard | PyPI | Choose it when exhaustive collection checking matters more than constant per-call work |
| jaxtyping | PyPI | Choose it for array shape and dtype annotations around JAX, NumPy, PyTorch, or TensorFlow code |
| pydantic | PyPI | Choose it when external input must be parsed, converted, and stored as a model |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

