typeddjango / awesome-python-typing

Collection of awesome Python types, stubs, plugins, and tools to work with them.

Home Page:https://github.com/typeddjango

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Request] Please list "beartype" in the "Dynamic type checkers" section

leycec opened this issue · comments

Thanks for all the awesome typing lists, @sobolevn, @orsinium, and other fearless contributors. If someone could find a spare moment to add a terse link to beartype, a recently released constant-time (i.e., O(1) with negligible constants at call time regardless of container size) runtime type checker compliant with Python 3.6—3.9 and PEPs 483, 484, 540, 560, 563, 593 and soon to be compliant with PEP 585 that I personally maintain, that would be... like, super awesome: e.g.,

  • beartype - Constant-time runtime type checker.

...or something. I leave everything to your skilled and talented hands.

Thanks again. This has been an extreme year for humanity, so I humbly appreciate everyone's continued focus on this small (but crucial) slice of the Python pie. You're all awesome!

Oh wow, beartype looks amazing! I will totally add it.
I will also would love to learn how your List[int] support is implemented. I have tried several libraries, but they usually fail on this type.

Thanks so much for the kind comments! I've been bear-wrestling with PEP 585 all evening, so that means more than you know.

I love fielding questions like this, too. beartype not only supports shallow type hints like List[int] but also deeply nested constructs like Union[dict, float, int, Sequence[Union[dict, float, int, MutableSequence[str]]]] – which I just pulled verbatim from our voluminous test suite, so that surprisingly behaves as expected. Phew.

For the specific case of List[int], beartype dynamically generates a new wrapper function randomly type-checking one item of the passed or returned list to be an integer on each call – preserving our O(1) call-time guarantee while also guaranteeing eventual coverage over the entire list after a sufficient number of calls. Specifically, the expected number of calls required to type-check all items of a list of n items to be integers is O(n log n). So, given this:

from beartype import beartype
from typing import List

@beartype
def thoracic_spine_collapser(nerve_compression: List[int]) -> int:
    return nerve_compression[0] if nerve_compression else 42

beartype dynamically generates this new wrapper function:

def __beartyped_thoracic_spine_collapser(
    *args,
    __beartype_func=__beartype_func,
    __beartypistry=__beartypistry,
    **kwargs
):
    # Generate and localize a sufficiently large pseudo-random integer for
    # subsequent indexation in type-checking randomly selected container items.
    __beartype_random_int = __beartype_getrandbits(32)
    # Localize the number of passed positional arguments for efficiency.
    __beartype_args_len = len(args)
    # Localize this positional or keyword parameter if passed *OR* to the
    # sentinel value "__beartypistry" guaranteed to never be passed otherwise.
    __beartype_pith_0 = (
        args[0] if __beartype_args_len > 0 else
        kwargs.get('nerve_compression', __beartypistry)
    )

    # If this parameter was passed...
    if __beartype_pith_0 is not __beartypistry:
        # Type-check this passed parameter or return value against this
        # PEP-compliant type hint.
        if not (
            # True only if this pith shallowly satisfies this hint.
            isinstance(__beartype_pith_0, list) and
            # True only if either this pith is empty *OR* this pith is
            # both non-empty and deeply satisfies this hint.
            (not __beartype_pith_0 or isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], int))
        ):
            __beartype_raise_pep_call_exception(
                func=__beartype_func,
                pith_name='nerve_compression',
                pith_value=__beartype_pith_0,
            )

    # Call this function with all passed parameters, type-check the value
    # returned from this call against this PEP-noncompliant type hint, and
    # return this value only if this check succeeds.
    __beartype_return_value = __beartype_func(*args, **kwargs)
    if not isinstance(__beartype_return_value, __beartype_func.__annotations__['return']):
        raise __beartype_nonpep_return_exception(
            '@beartyped thoracic_spine_collapser() return value {} not {!r}.'.format(
                __beartype_trim(__beartype_return_value), __beartype_func.__annotations__['return']))
    return __beartype_return_value

There are more than a few interesting tidbits to unpack here, including:

  • The code comments above are verbatim as they appear in the generated code. Noice.
  • __beartype_random_int, a randomly generated 32-bit integer. If a pure-Python list contains more than 2**32 items, the user probably has bigger problems. 🙉
  • __beartypistry, an internal thread-safe global registry of all types, tuples of types, and forward references to currently undeclared types in type hints in callables decorated by beartype. Yup. Lotsa well-tested black magic there.
  • The line (not __beartype_pith_0 or isinstance(__beartype_pith_0[__beartype_random_int % len(__beartype_pith_0)], int)) is where the specific magic of randomly testing list items to be integers happens, naturally accounting for the edge case of empty lists.

Things get really interesting when beartype dynamically generates wrapper functions for callables annotated with deeply nested type hints like List[List[List[int]]], because we actually leverage PEP 572-style assignment expressions to localize repeatedly accessed random list items. But... I think I've exhausted us all enough for an evening.

Thanks again, @sobolevn! Come to think, I should probably write all of that up into a new subsection of our README.rst publicly exhibiting these shenanigans. Can't let a good opportunity to pontificate online go to waste, right? That feeling when you realize you should have become a professor after all.

I should probably write all of that up into a new subsection of our README.rst publicly exhibiting these shenanigans

And not only README, I would appreciate a series of articles about it.

That feeling when you realize you should have become a professor after all.

Totally! 👍

...I would appreciate a series of articles about it.

Aww. That's good feels. @albanie, my favourite machine-learning guy, also requested a full-on tech report for academic citation. My destiny is now clear. My wife and I are resurrecting our respective blogs sometime this winter, so... we now know what the first few hundred posts will endlessly belabour until everyone realizes I am boring.

Canada: what else you gonna do? Rolls up ice-encrusted sleeves. 🏔️ 🧊 ❄️

Rolls up ice-encrusted sleeves.

🇷🇺 approves!

It's not quite the article series we'd all hoped for, but our homepage now deep-dives into code generated by @beartype for hopefully interesting real-world use cases, including the aforementioned List[List[List[int]]] type hint. It escalates pretty fast.

Thanks for inspiring that descent into madness, @sobolevn.
—– from bone-chilling, mind-numbing Canada with ❤️

Added to the list.

Will have a look at the example, thanks!
By the way, I invite you to join our community of type enthusiasts on Telegram: https://t.me/drypython