Skip to main content

Unbearably fast runtime type checking in pure Python.

Project description

GitHub Actions status

Look for the bare necessities,
  the simple bare necessities.
Forget about your worries and your strife.

                        — The Jungle Book.

Beartype is an open-source pure-Python runtime type checker emphasizing efficiency, portability, and thrilling puns.

Unlike comparable static type checkers operating at the coarse-grained application level (e.g., Pyre, mypy, pyright, pytype), beartype operates exclusively at the fine-grained callable level of pure-Python functions and methods via the standard decorator design pattern. This renders beartype natively compatible with all interpreters and compilers targeting the Python language – including CPython, PyPy, Numba, and Nuitka.

Unlike comparable runtime type checkers (e.g., enforce, pytypes, typeguard), beartype wraps each decorated callable with a dynamically generated wrapper efficiently type-checking that specific callable. Since “performance by default” is our first-class concern, all wrappers are guaranteed to:

  • Exhibit O(1) time complexity with negligible constant factors.

  • Be either more efficient (in the common case) or exactly as efficient minus the cost of an additional stack frame (in the worst case) as equivalent type-checking implemented by hand, which no one should ever do.

Beartype thus brings Rust- and C++-inspired zero-cost abstractions into the lawless world of pure Python.

Beartype is portably implemented in pure Python 3, continuously stress-tested with GitHub Actions + tox + pytest, and permissively distributed under the MIT license. Beartype has no runtime dependencies, only one test-time dependency, and supports all Python 3.x releases still in active development.


Installation

Let’s install beartype with pip, because community standards are good:

pip3 install beartype

Let’s install beartype with Anaconda, because corporate standards are sometimes good, too:

conda config --add channels conda-forge
conda install beartype

Cheatsheet

Let’s type-check like greased lightning:

# Import the core @beartype decorator.
from beartype import beartype

# Import PEP-compliant types for use with @beartype.
from typing import List, Optional, Union

# Import beartype-specific types for use with @beartype, too.
from beartype.cave import (
    AnyType,
    BoolType,
    FunctionTypes,
    CallableTypes,
    GeneratorType,
    IntOrFloatType,
    IntType,
    IterableType,
    IteratorType,
    NoneType,
    NoneTypeOr,
    NumberType,
    RegexTypes,
    ScalarTypes,
    SequenceType,
    StrType,
    VersionTypes,
)

# Import user-defined types for use with @beartype, three.
from my_package.my_module import MyClass

# Decorate functions with @beartype and...
@beartype
def bare_necessities(
    # Annotate builtin types as is.
    param_must_satisfy_builtin_type: str,

    # Annotate user-defined types as is, too.
    param_must_satisfy_user_type: MyClass,

    # Annotate PEP-compliant types predefined by the "typing" module.
    param_must_satisfy_pep_type: List[int],

    # Annotate beartypes-specific types predefined by the beartype cave.
    param_must_satisfy_beartype_type_from_cave: NumberType,

    # Annotate PEP-compliant unions of types.
    param_must_satisfy_pep_union: Union[dict, MyClass, int,],

    # Annotate beartype-specific unions of types as tuples, too.
    param_must_satisfy_beartype_union: (dict, MyClass, int,),

    # Annotate beartype-specific unions predefined by the beartype cave.
    param_must_satisfy_beartype_union_from_cave: CallableTypes,

    # Annotate beartype-specific unions concatenated together.
    param_must_satisfy_beartype_union_concatenated: (
        IteratorType,) + ScalarTypes,

    # Annotate beartype-specific forward references dynamically resolved
    # at first call time as fully-qualified "."-delimited classnames.
    param_must_satisfy_beartype_forward_ref: 'my_package.my_module.MyClass',

    # Annotate beartype-specific forward references in unions of types, too.
    param_must_satisfy_beartype_union_with_forward_ref: (
        IterableType, 'my_package.my_module.MyOtherClass', NoneType,),

    # Annotate PEP-compliant optional types.
    param_must_satisfy_pep_type_optional: Optional[float] = None,

    # Annotate beartype-specific optional types.
    param_must_satisfy_beartype_type_optional: NoneTypeOr[float] = None,

    # Annotate PEP-compliant optional unions of types.
    param_must_satisfy_pep_tuple_optional: Optional[Union[float, int]]) = None,

    # Annotate beartype-specific optional unions of types.
    param_must_satisfy_beartype_tuple_optional: NoneTypeOr[float, int] = None,

    # Annotate variadic positional arguments as above, too.
    *args: VersionTypes + (IntOrFloatType, 'my_package.my_module.MyVersionType',),

    # Annotate keyword-only arguments as above, too.
    param_must_be_passed_by_keyword_only: SequenceType,

# Annotate return types as above, too.
) -> (IntType, 'my_package.my_module.MyOtherOtherClass', BoolType):
    return 0xDEADBEEF


# Decorate generators as above but returning a generator type.
@beartype
def bare_generator() -> GeneratorType:
    yield from range(0xBEEFBABE, 0xCAFEBABE)


class MyCrassClass:
    # Decorate instance methods as above without annotating "self".
    @beartype
    def __init__(self, scalar: ScalarTypes) -> NoneType:
        self._scalar = scalar

    # Decorate class methods as above without annotating "cls". When
    # chaining decorators, "@beartype" should typically be specified last.
    @classmethod
    @beartype
    def bare_classmethod(cls, regex: RegexTypes, wut: str) -> FunctionTypes:
        import re
        return lambda: re.sub(regex, 'unbearable', str(cls._scalar) + wut)

    # Decorate static methods as above.
    @staticmethod
    @beartype
    def bare_staticmethod(callable: CallableTypes, *args: str) -> AnyType:
        return callable(*args)

    # Decorate property getter methods as above.
    @property
    @beartype
    def bare_gettermethod(self) -> IteratorType:
        return range(0x0B00B135 + int(self._scalar), 0xB16B00B5)

    # Decorate property setter methods as above.
    @bare_gettermethod.setter
    @beartype
    def bare_settermethod(self, bad: IntType = 0xBAAAAAAD) -> NoneType:
        self._scalar = bad if bad else 0xBADDCAFE

Timings

Let’s run our profiler suite quantitatively timing beartype and fellow runtime type-checkers against a battery of surely fair, impartial, and unbiased use cases:

beartype profiler [version]: 0.0.1

python    [version]: Python 3.7.8
beartype  [version]: 0.2.0
typeguard [version]: 2.9.1

========================== str (100 calls each loop) ==========================
decoration         [none     ]: 100 loops, best of 3: 351 nsec per loop
decoration         [beartype ]: 100 loops, best of 3: 351 usec per loop
decoration         [typeguard]: 100 loops, best of 3: 12.9 usec per loop
decoration + calls [none     ]: 100 loops, best of 3: 15.6 usec per loop
decoration + calls [beartype ]: 100 loops, best of 3: 486 usec per loop
decoration + calls [typeguard]: 100 loops, best of 3: 7.03 msec per loop

==================== Union[int, str] (100 calls each loop) ====================
decoration         [none     ]: 100 loops, best of 3: 2.9 usec per loop
decoration         [beartype ]: 100 loops, best of 3: 358 usec per loop
decoration         [typeguard]: 100 loops, best of 3: 16.9 usec per loop
decoration + calls [none     ]: 100 loops, best of 3: 18.5 usec per loop
decoration + calls [beartype ]: 100 loops, best of 3: 551 usec per loop
decoration + calls [typeguard]: 100 loops, best of 3: 11.3 msec per loop

=============== List[object] of 150 items (839 calls each loop) ===============
decoration         [none     ]: 100 loops, best of 1: 3.79 usec per loop
decoration         [beartype ]: 100 loops, best of 1: 341 usec per loop
decoration         [typeguard]: 100 loops, best of 1: 18.9 usec per loop
decoration + calls [none     ]: 100 loops, best of 1: 140 usec per loop
decoration + calls [beartype ]: 100 loops, best of 1: 1.4 msec per loop
decoration + calls [typeguard]: 100 loops, best of 1: 2.13 sec per loop

ELI5

On the one hand, beartype is:

  • At least twenty times faster (i.e., 20,000%) and consumes three orders of magnitude less time in the worst case than typeguard – the only comparable runtime type-checker also compatible with all modern versions of Python.

  • Infinitely faster in the best case than typeguard, which is sufficiently slow as to raise genuine usability and security concerns (e.g., application-layer Denial-of-Service (DoS) attacks).

  • Constant across type hints, taking roughly the same time to check parameters and return values hinted by the builtin type str as it does to check those hinted by the synthetic type Union[int, str] as it does to check those hinted by the container type List[object]. typeguard is variable across type hints, taking infinitely longer to check List[object] as as it does to check Union[int, str], taking roughly twice the time as it does to check str.

so that’s good

On the other hand, beartype is only partially compliant with annotation-centric Python Enhancement Proposals (PEPs) like PEP 484, whereas typeguard is (mostly) fully compliant with these PEPs. so that’s bad

On the gripping hand, beartype also intends to be (mostly) fully compliant with these PEPs by either the heat death of the known universe or the catastrophic implosion in reductive normalcy induced by collective first contact with a hyperchromatic condensation of self-transforming machine elves from self-dribbling jeweled basketballs (whichever comes first). so that’s… good?

https://user-images.githubusercontent.com/217028/91650639-92018a80-ea71-11ea-872e-10c1d296ed3d.png

But… How?

beartype performs the lion’s share of its work at decoration time. The @beartype decorator consumes most of the time needed to first decorate and then repeatedly call a decorated function. beartype is thus front-loaded. After paying the initial cost of decoration, each type-checked call thereafter incurs comparatively little overhead.

All other runtime type checkers perform the lion’s share of their work at call time. @typeguard.typechecked and similar decorators consume almost none of the time needed to first decorate and then repeatedly call a decorated function. They’re thus back-loaded. Although the initial cost of decoration is essentially free, each type-checked call thereafter incurs significant overhead.

Nobody Believes You

Math time, people. it’s happening

Most runtime type-checkers exhibit O(n) time complexity (where n is the total number of items recursively contained in a container to be checked) by recursively and repeatedly checking all items of all containers passed to or returned from all calls of decorated callables.

beartype guarantees O(1) time complexity by non-recursively but repeatedly checking one random item from each nesting level of all containers passed to or returned from all calls of decorated callables, thus amortizing the cost of checking items across calls.

Formally, beartype exploits the well-known coupon collector’s problem as applied to abstract trees of nested type hints. Let:

  • E(T) be the expected number of calls needed to check all items of a container containing only non-container items (i.e., containing no nested subcontainers) either passed to or returned from a @beartype-decorated callable.

  • γ ≈ 0.5772156649 be the Euler–Mascheroni constant.

Then:

https://render.githubusercontent.com/render/math?math=%5Cdisplaystyle+E%28T%29+%3D+n+%5Clog+n+%2B+%5Cgamma+n+%2B+%5Cfrac%7B1%7D%7B2%7D+%2B+O%5Cleft%28%5Cfrac%7B1%7D%7Bn%7D%5Cright%29

The summation ½ + O(1/n) is strictly less than 1 and thus negligible. While non-negligible, the term γn grows significantly slower than the term nlogn. So this reduces to:

https://render.githubusercontent.com/render/math?math=%5Cdisplaystyle+E%28T%29+%3D+O%28n+%5Clog+n%29

We now generalize this bound to the general case. When checking a container containing no subcontainers, beartype only randomly samples one item from that container on each call. When checking a container containing arbitrarily many nested subcontainers, however, beartype randomly samples one random item from each nesting level of that container on each call.

In general, beartype thus samples h random items from a container on each call, where h is that container’s height (i.e., maximum number of edges on the longest path from that container to a non-container leaf item reachable from items directly contained in that container). Since h ≥ 1, beartype samples at least as many items each call as assumed in the usual coupon collector’s problem and thus paradoxically takes a fewer number of calls on average to check all items of a container containing arbitrarily many subcontainers as it does to check all items of a container containing no subcontainers.

Ergo, the expected number of calls E(S) needed to check all items of an arbitrary container exhibits the same or better growth rate and remains bound above by at least the same upper bounds – but probably tighter: e.g.,

https://render.githubusercontent.com/render/math?math=%5Cdisplaystyle+E%28S%29+%3D+O%28E%28T%29%29+%3D+O%28n+%5Clog+n%29%0A

Fully checking a container takes no more calls than that container’s size times the logarithm of that size on average. For example, fully checking a list of 50 integers is expected to take 225 calls on average.

Usage

The @beartype decorator published by the beartype package transparently supports two fundamentally different types of callable type hints – each with its own tradeoffs, tribal dogmas, religious icons, and zealous code inquisitors:

  • Beartype-specific type hints, which:

    • Are highly performant in both space and time. (That’s good.) Efficiency is our raison d’être, after all. If your use case doesn’t need efficiency, consider adopting an alternate runtime type-checker more compatible with Python’s existing type-checking landscape – like typeguard.

    • Are incapable of deeply type-checking the contents, elements, items, metadata, structure, or other attributes of passed parameters and returned values. (That’s bad.)

    • Are fully supported by beartype. (That’s good.)

    • Do not comply with existing Python Enhancement Proposals (PEPs). (That’s bad, arguably.)

  • PEP-compliant type hints, which:

    • Are highly inefficient in both space and time. (That’s bad.)

    • Are capable of deeply type-checking the contents, elements, items, metadata, structure, and other attributes of passed parameters and returned values. (That’s good.)

    • Are only partially supported by beartype. (That’s bad.)

    • Comply with existing PEPs. (That’s good, arguably.)

Callers may freely intermingle these two types and thus obtain “the best of both worlds” when annotating parameters and return values. All else being equal, your maxim to type by beartype should be:

Use beartype-specific type hints
  where sufficient.
Use PEP-compliant type hints
  everywhere else.

Beartype-specific Type Hints

This is simpler than it sounds. Would we lie? Instead of answering that, let’s begin with the simplest type of type-checking supported by @beartype.

Builtin Types

Builtin types like dict, int, list, set, and str are trivially type-checked by annotating parameters and return values with those types as is.

Let’s declare a simple beartyped function accepting a string and a dictionary and returning a tuple:

from beartype import beartype

@beartype
def law_of_the_jungle(wolf: str, pack: dict) -> tuple:
    return (wolf, pack[wolf]) if wolf in pack else None

Let’s call that function with good types:

>>> law_of_the_jungle(wolf='Akela', pack={'Akela': 'alone', 'Raksha': 'protection'})
('Akela', 'alone')

Good function. Let’s call it again with bad types:

>>> law_of_the_jungle(wolf='Akela', pack=['Akela', 'Raksha'])
Traceback (most recent call last):
  File "<ipython-input-10-7763b15e5591>", line 1, in <module>
    law_of_the_jungle(wolf='Akela', pack=['Akela', 'Raksha'])
  File "<string>", line 22, in __law_of_the_jungle_beartyped__
beartype.roar.BeartypeCallTypeParamException: @beartyped law_of_the_jungle() parameter pack=['Akela', 'Raksha'] not a <class 'dict'>.

The beartype.roar submodule publishes exceptions raised at both decoration time by @beartype and at runtime by wrappers generated by @beartype. In this case, a runtime type exception describing the improperly typed pack parameter is raised.

Good function! Let’s call it again with good types exposing a critical issue in this function’s implementation and/or return type annotation:

>>> law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'})
Traceback (most recent call last):
  File "<ipython-input-10-7763b15e5591>", line 1, in <module>
    law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'})
  File "<string>", line 28, in __law_of_the_jungle_beartyped__
beartype.roar.BeartypeCallTypeReturnException: @beartyped law_of_the_jungle() return value None not a <class 'tuple'>.

Bad function. Let’s conveniently resolve this by permitting this function to return either a tuple or None as detailed below:

>>> from beartype.cave import NoneType
>>> @beartype
... def law_of_the_jungle(wolf: str, pack: dict) -> (tuple, NoneType):
...     return (wolf, pack[wolf]) if wolf in pack else None
>>> law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'})
None

The beartype.cave submodule publishes generic types suitable for use with the @beartype decorator and anywhere else you might need them. In this case, the type of the None singleton is imported from this submodule and listed in addition to tuple as an allowed return type from this function.

Note that usage of the beartype.cave submodule is entirely optional (but more efficient and convenient than most alternatives). In this case, the type of the None singleton can also be accessed directly as type(None) and listed in place of NoneType above: e.g.,

>>> @beartype
... def law_of_the_jungle(wolf: str, pack: dict) -> (tuple, type(None)):
...     return (wolf, pack[wolf]) if wolf in pack else None
>>> law_of_the_jungle(wolf='Leela', pack={'Akela': 'alone', 'Raksha': 'protection'})
None

Of course, the beartype.cave submodule also publishes types not accessible directly like RegexCompiledType (i.e., the type of all compiled regular expressions). All else being equal, beartype.cave is preferable.

Good function! The type hints applied to this function now accurately document this function’s API. All’s well that ends typed well. Suck it, Shere Khan.

Arbitrary Types

Everything above also extends to:

  • Arbitrary types like user-defined classes and stock classes in the Python stdlib (e.g., argparse.ArgumentParser) – all of which are also trivially type-checked by annotating parameters and return values with those types.

  • Arbitrary callables like instance methods, class methods, static methods, and generator functions and methods – all of which are also trivially type-checked with the @beartype decorator.

Let’s declare a motley crew of beartyped callables doing various silly things in a strictly typed manner, just ‘cause:

from beartype import beartype
from beartype.cave import GeneratorType, IterableType, NoneType

class MaximsOfBaloo(object):
    @beartype
    def __init__(self, sayings: IterableType):
        self.sayings = sayings

@beartype
def inform_baloo(maxims: MaximsOfBaloo) -> GeneratorType:
    for saying in maxims.sayings:
        yield saying

For genericity, the MaximsOfBaloo class initializer accepts any generic iterable (via the beartype.cave.IterableType tuple listing all valid iterable types) rather than an overly specific list or tuple type. Your users may thank you later.

For specificity, the inform_baloo generator function has been explicitly annotated to return a beartype.cave.GeneratorType (i.e., the type returned by functions and methods containing at least one yield statement). Type safety brings good fortune for the New Year.

Let’s iterate over that generator with good types:

>>> maxims = MaximsOfBaloo(sayings={
...     '''If ye find that the Bullock can toss you,
...           or the heavy-browed Sambhur can gore;
...      Ye need not stop work to inform us:
...           we knew it ten seasons before.''',
...     '''“There is none like to me!” says the Cub
...           in the pride of his earliest kill;
...      But the jungle is large and the Cub he is small.
...           Let him think and be still.''',
... })
>>> for maxim in inform_baloo(maxims): print(maxim.splitlines()[-1])
       Let him think and be still.
       we knew it ten seasons before.

Good generator. Let’s call it again with bad types:

>>> for maxim in inform_baloo([
...     'Oppress not the cubs of the stranger,',
...     '     but hail them as Sister and Brother,',
... ]): print(maxim.splitlines()[-1])
Traceback (most recent call last):
  File "<ipython-input-10-7763b15e5591>", line 30, in <module>
    '     but hail them as Sister and Brother,',
  File "<string>", line 12, in __inform_baloo_beartyped__
beartype.roar.BeartypeCallTypeParamException: @beartyped inform_baloo() parameter maxims=['Oppress not the cubs of the stranger,', '     but hail them as Sister and ...'] not a <class '__main__.MaximsOfBaloo'>.

Good generator! The type hints applied to these callables now accurately document their respective APIs. Thanks to the pernicious magic of beartype, all ends typed well… yet again.

Unions of Types

That’s all typed well, but everything above only applies to parameters and return values constrained to singular types. In practice, parameters and return values are often relaxed to any of multiple types referred to as unions of types. You can thank set theory for the jargon… unless you hate set theory. Then it’s just our fault.

Unions of types are trivially type-checked by annotating parameters and return values with tuples containing those types. Let’s declare another beartyped function accepting either a mapping or a string and returning either another function or an integer:

from beartype import beartype
from beartype.cave import FunctionType, IntType, MappingType

@beartype
def toomai_of_the_elephants(memory: (str, MappingType)) -> (
    IntType, FunctionType):
    return len(memory) if isinstance(memory, str) else lambda key: memory[key]

For genericity, the toomai_of_the_elephants function accepts any generic integer (via the beartype.cave.IntType abstract base class (ABC) matching both builtin integers and third-party integers from frameworks like NumPy and SymPy) rather than an overly specific int type. The API you relax may very well be your own.

Let’s call that function with good types:

>>> memory_of_kala_nag = {
...     'remember': 'I will remember what I was, I am sick of rope and chain—',
...     'strength': 'I will remember my old strength and all my forest affairs.',
...     'not sell': 'I will not sell my back to man for a bundle of sugar-cane:',
...     'own kind': 'I will go out to my own kind, and the wood-folk in their lairs.',
...     'morning':  'I will go out until the day, until the morning break—',
...     'caress':   'Out to the wind’s untainted kiss, the water’s clean caress;',
...     'forget':   'I will forget my ankle-ring and snap my picket stake.',
...     'revisit':  'I will revisit my lost loves, and playmates masterless!',
... }
>>> toomai_of_the_elephants(memory_of_kala_nag['remember'])
56
>>> toomai_of_the_elephants(memory_of_kala_nag)('remember')
'I will remember what I was, I am sick of rope and chain—'

Good function. Let’s call it again with a tastelessly bad type:

>>> toomai_of_the_elephants(0xDEADBEEF)
Traceback (most recent call last):
  File "<ipython-input-7-e323f8d6a4a0>", line 1, in <module>
    toomai_of_the_elephants(0xDEADBEEF)
  File "<string>", line 12, in __toomai_of_the_elephants_beartyped__
BeartypeCallTypeParamException: @beartyped toomai_of_the_elephants() parameter memory=3735928559 not a (<class 'str'>, <class 'collections.abc.Mapping'>).

Good function! The type hints applied to this callable now accurately documents its API. All ends typed well… still again and again.

Optional Types

That’s also all typed well, but everything above only applies to mandatory parameters and return values whose types are never NoneType. In practice, parameters and return values are often relaxed to optionally accept any of multiple types including NoneType referred to as optional types.

Optional types are trivially type-checked by annotating optional parameters (parameters whose values default to None) and optional return values (callables returning None rather than raising exceptions in edge cases) with the NoneTypeOr tuple factory indexed by those types or tuples of types.

Let’s declare another beartyped function accepting either an enumeration type or None and returning either an enumeration member or None:

from beartype import beartype
from beartype.cave import EnumType, EnumMemberType, NoneTypeOr
from enum import Enum

class Lukannon(Enum):
    WINTER_WHEAT = 'The Beaches of Lukannon—the winter wheat so tall—'
    SEA_FOG      = 'The dripping, crinkled lichens, and the sea-fog drenching all!'
    PLAYGROUND   = 'The platforms of our playground, all shining smooth and worn!'
    HOME         = 'The Beaches of Lukannon—the home where we were born!'
    MATES        = 'I met my mates in the morning, a broken, scattered band.'
    CLUB         = 'Men shoot us in the water and club us on the land;'
    DRIVE        = 'Men drive us to the Salt House like silly sheep and tame,'
    SEALERS      = 'And still we sing Lukannon—before the sealers came.'

@beartype
def tell_the_deep_sea_viceroys(story: NoneTypeOr[EnumType] = None) -> (
    NoneTypeOr[EnumMemberType]):
    return story if story is None else list(story.__members__.values())[-1]

For efficiency, the NoneTypeOr tuple factory creates, caches, and returns new tuples of types appending NoneType to the original types and tuples of types it’s indexed with. Since efficiency is good, NoneTypeOr is also good.

Let’s call that function with good types:

>>> tell_the_deep_sea_viceroys(Lukannon)
<Lukannon.SEALERS: 'And still we sing Lukannon—before the sealers came.'>
>>> tell_the_deep_sea_viceroys()
None

You may now be pondering to yourself grimly in the dark: “…but could we not already do this just by manually annotating optional types with tuples containing NoneType?”

You would, of course, be correct. Let’s grimly redeclare the same function accepting and returning the same types – only annotated with NoneType rather than NoneTypeOr:

from beartype import beartype
from beartype.cave import EnumType, EnumMemberType, NoneType

@beartype
def tell_the_deep_sea_viceroys(story: (EnumType, NoneType) = None) -> (
    (EnumMemberType, NoneType)):
    return list(story.__members__.values())[-1] if story is not None else None

This manual approach has the same exact effect as the prior factoried approach with one exception: the factoried approach efficiently caches and reuses tuples over every annotated type, whereas the manual approach inefficiently recreates tuples for each annotated type. For small codebases, that difference is negligible; for large codebases, that difference is still probably negligible. Still, “waste not want not” is the maxim we type our lives by here.

Naturally, the NoneTypeOr tuple factory accepts tuples of types as well. Let’s declare another beartyped function accepting either an enumeration type, enumeration type member, or None and returning either an enumeration type, enumeration type member, or None:

from beartype import beartype
from beartype.cave import EnumType, EnumMemberType, NoneTypeOr

EnumOrEnumMemberType = (EnumType, EnumMemberType)

@beartype
def sang_them_up_the_beach(
    woe: NoneTypeOr[EnumOrEnumMemberType] = None) -> (
    NoneTypeOr[EnumOrEnumMemberType]):
    return woe if isinstance(woe, NoneTypeOr[EnumMemberType]) else (
        list(woe.__members__.values())[-1])

Let’s call that function with good types:

>>> sang_them_up_the_beach(Lukannon)
<Lukannon.SEALERS: 'And still we sing Lukannon—before the sealers came.'>
>>> sang_them_up_the_beach()
None

Behold! The terrifying power of the NoneTypeOr tuple factory, resplendent in its highly over-optimized cache utilization.

PEP-compliant Type Hints

beartype is fully compliant with these Python Enhancement Proposals (PEPs):

beartype is partially compliant with these PEPs:

beartype is currently not compliant whatsoever with these PEPs:

See also the PEP and typing categories of our features matrix for further details.

PEP 484 Compliance

beartype is only partially compliant with PEP 483 and 484. Let’s see what that means in practice.

Deep (Full) Compliance

beartype deeply type-checks (i.e., both directly checks the types of and recursively checks the types of items contained in) callable parameters and return values annotated by these typing types:

  • typing.Any.

  • typing.Optional.

  • typing.Union.

Shallow (Partial) Compliance

beartype currently only shallowly type-checks (i.e., only directly checks the types of) callable parameters and return values annotated by these typing types:

  • typing.AbstractSet.

  • typing.AsyncIterable.

  • typing.AsyncIterator.

  • typing.Awaitable.

  • typing.ByteString.

  • typing.Callable.

  • typing.ChainMap.

  • typing.Container.

  • typing.Coroutine.

  • typing.Counter.

  • typing.DefaultDict.

  • typing.Deque.

  • typing.Dict.

  • typing.FrozenSet.

  • typing.Generator.

  • typing.Hashable.

  • typing.ItemsView.

  • typing.Iterable.

  • typing.Iterator.

  • typing.KeysView.

  • typing.List.

  • typing.MappingView.

  • typing.Mapping.

  • typing.MutableMapping.

  • typing.MutableSequence.

  • typing.MutableSet.

  • typing.NamedTuple.

  • typing.Sequence.

  • typing.Set.

  • typing.Sized.

  • typing.Tuple.

  • typing.Type.

  • typing.TypedDict.

  • typing.ValuesView.

  • typing.SupportsAbs.

  • typing.SupportsBytes.

  • typing.SupportsComplex.

  • typing.SupportsInt.

  • typing.SupportsFloat.

  • typing.SupportsRound.

Subsequent beartype versions will deeply type-check these typing types while preserving our O(1) time complexity (with negligible constant factors) guarantee.

No Compliance

beartype currently raises exceptions at decoration time when passed these typing types:

  • Forward references (i.e., unqualified relative string classnames internally coerced by typing into typing.ForwardRef instances).

  • Forward reference-subscripted types (i.e., typing objects subscripted by one or more type forward references).

  • Type variables (i.e., typing.TypeVar instances enabling general-purpose type-checking of generically substitutable types).

  • Type variable-parametrized types (i.e., typing objects subscripted by one or more type variables).

  • User-defined generics (i.e., user-defined classes subclassing one or more typing non-classes).

  • User-defined protocols (i.e., user-defined classes transitively subclassing the typing.Protocol abstract base class (ABC)).

  • typing.AnyStr.

  • typing.BinaryIO.

  • typing.IO.

  • typing.Match.

  • typing.NewType.

  • typing.NoReturn.

  • typing.Pattern.

  • typing.TextIO.

Subsequent beartype versions will first shallowly and then deeply type-check these typing types while preserving our O(1) time complexity (with negligible constant factors) guarantee.

Features

Let’s chart current and prospective new features for future generations:

category

feature

versions

note

decoratable

classes

none

coroutines

none

functions

0.1.0current

generators

0.1.0current

methods

0.1.0current

parameters

optional

0.1.0current

keyword-only

0.1.0current

positional-only

none

variadic keyword

none

variadic positional

0.1.0current

hints

covariant

0.1.0current

contravariant

none

absolute forward references

0.1.0current

relative forward references

none

tuple unions

0.1.0current

typing

AbstractSet

0.2.0current

Any

0.2.0current

AnyStr

none

AsyncContextManager

0.2.0current

AsyncGenerator

0.2.0current

AsyncIterable

0.2.0current

AsyncIterator

0.2.0current

Awaitable

0.2.0current

BinaryIO

none

ByteString

0.2.0current

ChainMap

0.2.0current

Collection

0.2.0current

Container

0.2.0current

ContextManager

0.2.0current

Coroutine

0.2.0current

Counter

0.2.0current

DefaultDict

0.2.0current

Deque

0.2.0current

Dict

0.2.0current

Callable

0.2.0current

ForwardRef

none

FrozenSet

0.2.0current

Generator

0.2.0current

Generic

none

Hashable

0.2.0current

IO

none

ItemsView

0.2.0current

Iterable

0.2.0current

Iterator

0.2.0current

KeysView

0.2.0current

List

0.2.0current

Mapping

0.2.0current

MappingView

0.2.0current

Match

none

MutableMapping

0.2.0current

MutableSequence

0.2.0current

MutableSet

0.2.0current

NamedTuple

0.1.0current

NewType

none

NoReturn

none

Optional

0.2.0current

OrderedDict

0.2.0current

Pattern

none

Protocol

none

Reversible

0.2.0current

Sequence

0.2.0current

Set

0.2.0current

Sized

0.2.0current

SupportsAbs

0.2.0current

SupportsBytes

0.2.0current

SupportsComplex

0.2.0current

SupportsFloat

0.2.0current

SupportsIndex

0.2.0current

SupportsInt

0.2.0current

SupportsRound

0.2.0current

Text

0.1.0current

TextIO

none

Tuple

0.2.0current

Type

0.2.0current

TypedDict

0.1.0current

TypeVar

none

ValuesView

0.2.0current

Union

0.2.0current

final

none

PEP

484

0.2.0current

544

none

563

0.1.1current

585

none

586

none

589

none

packages

PyPI

0.2.0current

Anaconda

0.2.0current

Python

3.5

0.1.0current

3.6

0.1.0current

3.7

0.1.0current

3.8

0.1.0current

License

beartype is open-source software released under the permissive MIT license.

Funding

beartype is currently financed as a purely volunteer open-source project – which is to say, it’s unfinanced. Prior funding sources (yes, they once existed) include:

  1. Over the period 2015—2018 preceding the untimely death of Paul Allen, beartype was graciously associated with the Paul Allen Discovery Center at Tufts University and grant-funded by a Paul Allen Discovery Center award from the Paul G. Allen Frontiers Group through its parent applications – the multiphysics biology simulators BETSE and BETSEE.

See Also

Runtime type checkers (i.e., third-party mostly pure-Python packages dynamically validating Python callable types at Python runtime, typically via decorators, explicit function calls, and import hooks) include:

Static type checkers (i.e., third-party tooling not implemented in Python statically validating Python callable and/or variable types across a full application stack at tool rather than Python runtime) include:

  • mypy.

  • Pyre, published by FaceBook. …yah.

  • pyright, published by Microsoft.

  • pytype, published by Google.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

beartype-0.2.0.tar.gz (223.2 kB view hashes)

Uploaded Source

Built Distribution

beartype-0.2.0-py3-none-any.whl (167.5 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page