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 PEP-compliant 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 decorated callables with dynamically generated wrappers efficiently type-checking those specific callables. Since “performance by default” is our first-class concern, these 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 via 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 (occasionally) good too:

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

Linux

Let’s install beartype with Gentoo, because source-based Linux distros are the computational nuclear option:

emerge --ask app-eselect/eselect-repository
mkdir -p /etc/portage/repos.conf
eselect repository enable raiagent
emerge --sync raiagent
emerge beartype

Cheatsheet

Let’s type-check like greased lightning:

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

# Import PEP 585-compliant types to annotate callables with.
from collections.abc import MutableSequence

# Import PEP 484-compliant types to annotate callables with, too.
from typing import List, Optional, Tuple, Union

# Import beartype-specific types to annotate callables with, 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 classes as is, too. Note this covariantly
    # matches all instances of both this class and subclasses of this class.
    param_must_satisfy_user_type: MyClass,

    # Annotate PEP 585-compliant builtin container types, subscripted by the
    # types of items these containers are required to contain.
    param_must_satisfy_pep585_builtin: list[str],

    # Annotate PEP 585-compliant standard collection types, subscripted too.
    param_must_satisfy_pep585_collection: MutableSequence[str],

    # Annotate PEP 484-compliant non-standard container types defined by the
    # "typing" module, optionally subscripted and only usable as type hints.
    param_must_satisfy_pep484_typing: List[int],

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

    # Annotate PEP 484-compliant relative forward references dynamically
    # resolved at call time as unqualified classnames relative to the
    # current user-defined submodule. Note this class is defined below.
    param_must_satisfy_pep484_relative_forward_ref: 'MyCrassClass',

    # Annotate PEP 484-compliant objects predefined by the "typing" module
    # subscripted by PEP-compliant relative forward references.
    param_must_satisfy_pep484_hint_relative_forward_ref: (
        List['MyCrassClass']),

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

    # Annotate beartype-specific unions of types as tuples.
    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 absolute forward references dynamically
    # resolved at call time as fully-qualified "."-delimited classnames.
    param_must_satisfy_beartype_absolute_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 484-compliant optional types.
    param_must_satisfy_pep484_optional: Optional[float] = None,

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

    # Annotate beartype-specific optional types.
    param_must_satisfy_beartype_type_optional: NoneTypeOr[float] = 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.2

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

========================== str (100 calls each loop) ==========================
decoration         [none     ]: 100 loops, best of 3: 366 nsec per loop
decoration         [beartype ]: 100 loops, best of 3: 346 usec per loop
decoration         [typeguard]: 100 loops, best of 3: 13.4 usec per loop
decoration + calls [none     ]: 100 loops, best of 3: 16.4 usec per loop
decoration + calls [beartype ]: 100 loops, best of 3: 480 usec per loop
decoration + calls [typeguard]: 100 loops, best of 3: 7 msec per loop

==================== Union[int, str] (100 calls each loop) ====================
decoration         [none     ]: 100 loops, best of 3: 2.97 usec per loop
decoration         [beartype ]: 100 loops, best of 3: 363 usec per loop
decoration         [typeguard]: 100 loops, best of 3: 16.7 usec per loop
decoration + calls [none     ]: 100 loops, best of 3: 20.4 usec per loop
decoration + calls [beartype ]: 100 loops, best of 3: 543 usec per loop
decoration + calls [typeguard]: 100 loops, best of 3: 11.1 msec per loop

================ List[int] of 1000 items (7485 calls each loop) ================
decoration         [none     ]: 1 loop, best of 1: 41.7 usec per loop
decoration         [beartype ]: 1 loop, best of 1: 1.33 msec per loop
decoration         [typeguard]: 1 loop, best of 1: 82.2 usec per loop
decoration + calls [none     ]: 1 loop, best of 1: 1.4 msec per loop
decoration + calls [beartype ]: 1 loop, best of 1: 22.5 msec per loop
decoration + calls [typeguard]: 1 loop, best of 1: 124 sec per loop

ELI5

On the one hand, beartype is:

  • At least twenty times faster (i.e., 20,000%) and takes 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.

  • Asymptotically faster in the best case than typeguard, which scales linearly (rather than not at all) with the size of checked containers.

  • 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

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.

beartype exploits the well-known coupon collector’s problem applied to abstract trees of nested type hints, enabling us to statistically predict the number of calls required to fully type-check all items of an arbitrary container on average. Formally, 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.

Compliance

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

beartype is currently not compliant whatsoever with these PEPs:

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

Full Compliance

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

beartype also fully supports callables decorated by these typing decorators:

Lastly, beartype fully supports these typing constants:

Partial Compliance

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

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 silently ignores these typing types at decoration time:

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

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.

Tutorial

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.

Features

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

category

feature

versions partially supporting

versions fully supporting

decoratable

classes

none

none

coroutines

none

none

functions

0.1.0current

0.1.0current

generators

0.1.0current

0.1.0current

methods

0.1.0current

0.1.0current

parameters

optional

0.1.0current

0.1.0current

keyword-only

0.1.0current

0.1.0current

positional-only

none

none

variadic keyword

none

none

variadic positional

0.1.0current

0.1.0current

hints

covariant

0.1.0current

0.1.0current

contravariant

none

none

absolute forward references

0.1.0current

0.1.0current

relative forward references

0.4.0current

0.4.0current

tuple unions

0.1.0current

0.1.0current

builtins

dict

0.5.0current

none

frozenset

0.5.0current

none

list

0.5.0current

0.5.0current

set

0.5.0current

none

tuple

0.5.0current

0.5.0current

type

0.5.0current

none

collections

collections.ChainMap

0.5.0current

none

collections.Counter

0.5.0current

none

collections.OrderedDict

0.5.0current

none

collections.defaultdict

0.5.0current

none

collections.deque

0.5.0current

none

collections.abc

collections.abc.AsyncGenerator

0.5.0current

none

collections.abc.AsyncIterable

0.5.0current

none

collections.abc.AsyncIterator

0.5.0current

none

collections.abc.Awaitable

0.5.0current

none

collections.abc.ByteString

0.5.0current

0.5.0current

collections.abc.Callable

0.5.0current

none

collections.abc.Collection

0.5.0current

none

collections.abc.Container

0.5.0current

none

collections.abc.Coroutine

0.5.0current

none

collections.abc.Generator

0.5.0current

none

collections.abc.ItemsView

0.5.0current

none

collections.abc.Iterable

0.5.0current

none

collections.abc.Iterator

0.5.0current

none

collections.abc.KeysView

0.5.0current

none

collections.abc.Mapping

0.5.0current

none

collections.abc.MappingView

0.5.0current

none

collections.abc.MutableMapping

0.5.0current

none

collections.abc.MutableSequence

0.5.0current

0.5.0current

collections.abc.MutableSet

0.5.0current

none

collections.abc.Reversible

0.5.0current

none

collections.abc.Sequence

0.5.0current

0.5.0current

collections.abc.Set

0.5.0current

none

collections.abc.ValuesView

0.5.0current

none

contextlib

contextlib.AbstractAsyncContextManager

0.5.0current

none

contextlib.AbstractContextManager

0.5.0current

none

re

re.Match

0.5.0current

none

re.Pattern

0.5.0current

none

typing

typing.AbstractSet

0.2.0current

none

typing.Annotated

0.4.0current

0.4.0current

typing.Any

0.2.0current

0.2.0current

typing.AnyStr

0.4.0current

none

typing.AsyncContextManager

0.4.0current

none

typing.AsyncGenerator

0.2.0current

none

typing.AsyncIterable

0.2.0current

none

typing.AsyncIterator

0.2.0current

none

typing.Awaitable

0.2.0current

none

typing.BinaryIO

0.4.0current

none

typing.ByteString

0.2.0current

0.2.0current

typing.Callable

0.2.0current

none

typing.ChainMap

0.2.0current

none

typing.ClassVar

none

none

typing.Collection

0.2.0current

none

typing.Container

0.2.0current

none

typing.ContextManager

0.4.0current

none

typing.Coroutine

0.2.0current

none

typing.Counter

0.2.0current

none

typing.DefaultDict

0.2.0current

none

typing.Deque

0.2.0current

none

typing.Dict

0.2.0current

none

typing.Final

none

none

typing.ForwardRef

0.4.0current

0.4.0current

typing.FrozenSet

0.2.0current

none

typing.Generator

0.2.0current

none

typing.Generic

0.4.0current

0.4.0current

typing.Hashable

0.2.0current

none

typing.IO

0.4.0current

none

typing.ItemsView

0.2.0current

none

typing.Iterable

0.2.0current

none

typing.Iterator

0.2.0current

none

typing.KeysView

0.2.0current

none

typing.List

0.2.0current

0.3.0current

typing.Literal

none

none

typing.Mapping

0.2.0current

none

typing.MappingView

0.2.0current

none

typing.Match

0.4.0current

none

typing.MutableMapping

0.2.0current

none

typing.MutableSequence

0.2.0current

0.3.0current

typing.MutableSet

0.2.0current

none

typing.NamedTuple

0.1.0current

none

typing.NewType

0.4.0current

0.4.0current

typing.NoReturn

0.4.0current

0.4.0current

typing.Optional

0.2.0current

0.2.0current

typing.OrderedDict

0.2.0current

none

typing.Pattern

0.4.0current

none

typing.Protocol

0.4.0current

0.4.0current

typing.Reversible

0.2.0current

none

typing.Sequence

0.2.0current

0.3.0current

typing.Set

0.2.0current

none

typing.Sized

0.2.0current

0.2.0current

typing.SupportsAbs

0.4.0current

0.4.0current

typing.SupportsBytes

0.4.0current

0.4.0current

typing.SupportsComplex

0.4.0current

0.4.0current

typing.SupportsFloat

0.4.0current

0.4.0current

typing.SupportsIndex

0.4.0current

0.4.0current

typing.SupportsInt

0.4.0current

0.4.0current

typing.SupportsRound

0.4.0current

0.4.0current

typing.Text

0.1.0current

0.1.0current

typing.TextIO

0.4.0current

none

typing.Tuple

0.2.0current

0.4.0current

typing.Type

0.2.0current

none

typing.TypedDict

0.1.0current

none

typing.TypeVar

0.4.0current

none

typing.Union

0.2.0current

0.2.0current

typing.ValuesView

0.2.0current

none

typing.TYPE_CHECKING

0.5.0current

0.5.0current

@typing.final

none

none

@typing.no_type_check

0.5.0current

0.5.0current

PEP

484

0.2.0current

none

544

0.4.0current

0.4.0current

560

0.4.0current

0.4.0current

563

0.1.1current

0.1.1current

572

0.3.0current

0.4.0current

585

0.5.0current

0.5.0current

586

none

none

589

none

none

591

none

none

593

0.4.0current

0.4.0current

packages

PyPI

0.1.0current

Anaconda

0.1.0current

Gentoo

0.2.0current

Python

3.5

0.1.00.3.0

3.6

0.1.0current

3.7

0.1.0current

3.8

0.1.0current

3.9

0.3.2current

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.5.0.tar.gz (324.0 kB view hashes)

Uploaded Source

Built Distribution

beartype-0.5.0-py3-none-any.whl (248.1 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