-
-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The Future Sound of Beartype: Going Deep on Type-checking #7
Comments
This commit mildly revises our front-facing "README.rst" to note our newly pinned issue #7, detailing the roadmap to beartype 1.0.0, as well as listing "typing" objects erroneously omitted from our "Partial Compliance" subsection. (*Faustian exhausts exhume inhuman inhumations!*)
Paging @albanie, both because you were right about exponential growth ...still didn't expect that and because the Beyond Typing PEPs, There Lies Third-Party Typing subsection above was written with mostly you in mind. Can't let those early adopters down! |
Predictably epic 😍 |
There's another new section I just excreted you might be interested in, too: Beartype Configuration API: It's Happening. This is the thing that will let you tell Let me know how that API works for you. It'll be baked into granite at some point and then we'll all have to live with my permanently dodgy design.
oh, you
Right. Last week, I was shocked (both pleasantly and unpleasantly) to find that I couldn't believe it. It's still hard to believe. Like, no one amongst the eight billion-fold tribal humans dispersed across the fragile surface of this beautiful planet thought to try anything but "full fat" runtime type-checking? Guess not. So, I can't and shouldn't fault Python developers. They're the only ones willing to do this largely thankless standardization work. At least something was standardized – even if it fell painfully short of the mark with respect to runtime introspection. Thanks, former BDFL Guido & Crew! Seriously. Thanks.
👊
...and I will worship them as data structure deities when that prophesy inevitably comes to pass. |
Update: new Influencer versus Introvert section where we outline the slow road to formal academic publication, informal blog posts, and... Twitter feeds? At least one of those things will happen. |
Interesting project. The inside/subtle/clever jokes get in the way of those trying to understand the project. In particular, most of the cleverness is lost on those of us who have no exposure to (or interest in) The Jungle Book, don't naturally associate bears with type checking, and just want to quickly grok the essence of what you've built. Consider writing a quick intro for clever boring people who aren't prepared to slog through the dozens of pages of otherwise-clever content? |
Fair and valid criticisms, one and all. I subscribe to the why the lucky stiff school of edumucation, but acknowledge that some (most) people "just want the facts, Mam." The Cheatsheet is for those people. If that's not quite cursory enough, the high-level one-sentence tl;dr exegesis for
Profit ensues. There's not much else to see, because the public
If that's a lot to swallow all at once (which it is), Real Python has an excellent write-up on Python annotations. We can and should improve on that here with a more readable introduction to the topic unique to Thanks for the interest, @kevinjacobs-progenity! Let me know if there's some low-hanging documentation fruit I can pluck here to make your stay more comfortable, simplistic, and self-explanatory. Also, Ann Arbor rocks. That's a heck of a University town you got there. 🏙️ |
Thanks, @leycec. Your response is actually very much along the lines of what a casual/impatient browser would want to see. Add in a very quick summary of what it means to achieve O(1) runtime cost (approximate and amortized value checking), a few technically non-trivial examples with typing errors (and boring code), and my concerns will be address. That said, I'm still trying to wrap my head around the implications of using your approach at runtime with my target application being medical device software (to aid with verification and obviously not in production). And thanks for being open minded and constructive about my feedback. Hope all is well in the farther snowy North. |
This commit significantly revises our front-facing "README.rst" in accordance with relevant concerns raised by @kevinjacobs-progenity at issue #7, including: * A new "Usage" section that accidentally became a new tutorial. Woops. * Division of the existing "Overview" section into various subsections highlighting tradeoffs between beartype and existing type checkers. I'd like to thanks @kevinjacobs-progenity for his invaluable commentary, without which beartype would be even less usable, which probably means unusable. (*Extorted tort lawyers contort conscripted drawers!*)
Thank you, Minion Kevin. Surprisingly funny and well-intended criticism like yours is invaluable, because other prospective users were thinking the same things but probably didn't want me to feel bad. I welcome feeling bad. That's how bad things get less bad. I pushed a few commits in your honour reworking our front-facing documentation for clarity. The Usage section is entirely new and intended to address the bulk of your concerns. Let me know if you have any remaining thoughts! We'll squash those, too.
Oh. Oh. A fellow biotech bro. Well, isn't that synchronous. My lovely wife is in corporate research for bleeding-edge (please not literally) spinal implants. Our prior work was multiphysics biology simulation for therapeutic tissue regeneration, so we're all old hats at this Python medical game. Let me know if you have any specific typing needs or concerns. Structural type-checking for NumPy arrays, Pandas dataframes, and loosely related scientific objects like tensors is planned post- Thanks again for the deep critique. We'll get there... someday. Until then, we play in the piles of fresh snow that hopefully will be coming soon. |
Hi, interesting project indeed! Although I'm also one of the boring "gimme the tl;dr" kinda people. But I do admire the dedication you positively radiate through the text! I'm here to give my two cents, although I don't understand the library quite yet:
All the best to you! |
Thanks for the kind commentary, @felix-hilden. Yup. I'm not so good at bringin' that sweet tl;dr yet. Let's hope for the sake of prolix nebulosity and my ailing sticky keyboard I level up on this in 2021.
Another 👍 for Behind the scenes, I'm obsessed throughout the remainder of January with roadmapping funding (...was that a collective "Ugh!" I just heard?) and core support for deep But... this will happen. Both because we all want this to happen and because that's probably where most of the actual funding opportunities lie. To paraphrase infamous JRPG developer Yoko Taro, I'll do just about anything if I get the money for it. Of course, I'll still do this and everything else without money. But money helps – especially with eating and other low-level primate activities.
That reassures my omnipresent Read The Docs + Sphinx + reStructuredText anxiety disorder. Fortunately, we currently have no API. wat Unfortunately, we will have an API at some point. That is the point at which this will happen. Unless someone does this for me first. Please do this for me first, someone.
And to you! 2020 was Spidey's Wild Ride and 2021 frankly didn't start any saner. I pray to a digital effigy of Richard Stallman every evening that sanity will be restored. Positive results should follow shortly, everyone. |
This release brings explicit support for ``None``, subscripted generics, and PEP 561 compliance. This release resolves **10 issues** and merges **8 pull requests.** Specific changes include: ## Compatibility Improved * **[PEP 484-compliant `None` singleton](https://www.python.org/dev/peps/pep-0484/#id16).** As a return type hint, `None` is typically used to annotate callables containing *no* explicit `return` statement and thus implicitly returning `None`. `@beartype` now implicitly reduces `None` at all nesting levels of type hints to that singleton's type per [PEP 484](https://www.python.org/dev/peps/pep-0484/#id16). * **[PEP 561 compliance](https://www.python.org/dev/peps/pep-0561).** `beartype` now fully conforms to [PEP 561](https://www.python.org/dev/peps/pep-0561), resolving issue #25 kindly submitted by best macOS package manager ever @harens. In useful terms, this means that: * **`beartype` now complies with [mypy](http://mypy-lang.org),** Python's popular third-party static type checker. If your package had no [mypy](http://mypy-lang.org) errors or warnings *before* adding `beartype` as a mandatory dependency, your package will still have no [mypy](http://mypy-lang.org) errors or warnings *after* adding `beartype` as a mandatory dependency. * **`beartype` preserves [PEP 561](https://www.python.org/dev/peps/pep-0561) compliance.** If your package was [PEP 561-compliant](https://www.python.org/dev/peps/pep-0561) *before* adding `beartype` as a mandatory dependency, your package will still be [PEP 561-compliant](https://www.python.org/dev/peps/pep-0561) *after* adding `beartype` as a mandatory dependency. Of course, if your package currently is *not* [PEP 561-compliant](https://www.python.org/dev/peps/pep-0561), `beartype` can't help you there. We'd love to, really. [It's us. Not you.](https://www.youtube.com/watch?v=2uAj4wBIU-8) * **The `beartype` codebase is now mostly statically rather than dynamically typed,** much to our public shame. Thus begins the eternal struggle to preserve duck typing in a world that hates bugs. * **The `beartype` package now contains a top-level `py.typed` file,** publicly declaring this package to be [PEP 561-compliant](https://www.python.org/dev/peps/pep-0561). * **Subscripted generics** (i.e., [user-defined generics](https://www.python.org/dev/peps/pep-0484) subscripted by one or more type hints), resolving issue #29 kindly submitted by indefatigable test engineer and anthropomorphic Siberian Husky @eehusky. Since it's best not to ask too many questions about subscripted generics, we instead refer you to [the issue report that nearly broke a Canadian man](#29). ## Compatibility Broken * **None.** This release preserves backward compatibility with the prior stable release. ## Packaging Improved * **New optional installation-time extras,** enabling both `beartype` developers and automation tooling to trivially install recommended (but technically optional) dependencies. These include: * `pip install -e .[dev]`, installing `beartype` in editable mode as well as all dependencies required to both locally test `beartype` *and* build documentation for `beartype` from the command line. * `pip install beartype[doc-rtd]`, installing `beartype` as well as all dependencies required to build documentation from the external third-party Read The Docs (RTD) host. * **Homebrew- and MacPorts-based macOS installation.** Our front-facing `README.rst` file now documents `beartype` installation with both Homebrew and MacPorts on macOS, entirely courtesy the third-party Homebrew tap and Portfile maintained by build automation specialist and mild-mannered student @harens. Thanks a London pound, Haren! ## Features Added * Public `beartype.cave` types and type tuples, including: * `beartype.cave.CallableCTypes`, a tuple of all **C-based callable types** (i.e., types whose instances are callable objects implemented in low-level C rather than high-level Python). * `beartype.cave.HintGenericSubscriptedType`, the C-based type of all subscripted generics if the active Python interpreter targets Python >= 3.9 *or* `beartype.cave.UnavailableType` otherwise. This type was previously named `beartype.cave.HintPep585Type` before we belatedly realized this type broadly applies to numerous categories of PEP-compliant type hints, including PEP 484-compliant subscripted generics. ## Features Optimized * **`O(n)` → `O(1)` exception handling.** `@beartype` now internally raises human-readable exceptions in the event of type-checking violations with an `O(1)` rather than `O(n)` algorithm, significantly reducing time complexity for the edge case of invalid large sequences either passed to or returned from `@beartype`-decorated callables. For forward compatibility with a future version of `beartype` enabling users to explicitly switch between constant- and linear-time checking, the prior `O(n)` exception-handling algorithm has been preserved in a presently disabled form. * **`O(n)` → `O(1)` callable introspection during internal memoization.** `@beartype` now avoids calling the inefficient stdlib `inspect` module from our private `@beartype._util.cache.utilcachecall.callable_cached` decorator memoizing functions throughout the `beartype` codebase. The prior `O(n)` logic performed by that call has been replaced by equivalent `O(1) logic performed by a call to our newly defined `beartype._util.func.utilfuncarg` submodule, optimizing function argument introspection without the unnecessary overhead of `inspect`. * **Code object caching.** `@beartype` now temporarily caches the code object for the currently decorated callable to support efficient introspection of that callable throughout the decoration process. Relatedly, this also has the beneficial side effect of explicitly raising human-readable exceptions from the `@beartype` decorator on attempting to decorate C-based callables, which `@beartype` now explicitly does *not* support, because C-based callables have *no* code objects and thus *no* efficient means of introspection. Fortunately, sane code only ever applies `@beartype` to pure-Python callables anyway. ...right, sane code? *Right!?!?* ## Features Deprecated * The ambiguously named `beartype.cave.HintPep585Type` type, to be officially removed in `beartype` 0.1.0. ## Issues Resolved * **Unsafe `str.replace()` calls.** `@beartype` now wraps all unsafe internal calls to the low-level `str.replace()` method with calls to the considerably safer high-level `beartype._util.text.utiltextmunge.replace_str_substrs()` function, guaranteeing that memoized placeholder strings are properly unmemoized during decoration-time code generation. Thanks to temperate perennial flowering plant @Heliotrop3 for this astute observation and resolution to long-standing background issue #11. * **`KeyPool` release validation.** `@beartype` now validates that objects passed to the `release()` method of the private `beartype._util.cache.pool.utilcachepool.KeyPool` class have been previously returned from the `acquire()` method of that class. Thanks to @Heliotrop3, the formidable bug assassin, for their unswerving dedication to the cause of justice with this resolution to issue #13. * **Least Recently Used (LRU) cache.** ``@beartype`` now internally provides a highly microoptimized Least Recently Used (LRU) cache for subsequent use throughout the codebase, particularly with respect to caching iterators over dictionaries, sets, and other non-sequence containers. This resolves issue #17, again graciously submitted by open-source bug mercenary @Heliotrop3. * **Callable labelling.** `@beartype` now internally provides a private `beartype._util.func.utilfuncorigin.get_callable_origin_label` getter synthesizing human-readable labels for the files declaring arbitrary callables, a contribution by master code-mangler @Heliotrop3 resolving issue #18. Thanks again for all the insidious improvements, Tyler! You are the master of everyone's code domain. * **Release automation.** Our release workflow has now been migrated from the unmaintained `create-release` GitHub Action to @ncipollo's actively maintained `release-action`, resolving issue #22 kindly submitted by human-AI-hybrid @Heliotrop3. ## Tests Improved * **Microsoft Windows and macOS exercised under CI**, resolving issue #21. Since doing so increases our consumption of Microsoft resources that we care deeply about, care has been taken to reduce the cost of our CI workflow. This includes: * Replacing our prior use of the external third-party `tox-gh-actions` GitHub Action streamlining `tox` usage with our own ad-hoc build matrix that appears to be simpler and faster despite offering basically identical functionality. * Removing our prior installation of optional dependencies, especially including NumPy. *Yeah.* Let's not do that anymore. Thanks to dedicated issue reporter @Heliotrop3 for his unsustainable deep-code trawling of the `beartype` codebase for unresolved `FIXME:` comments. * **PyPy 3.7 exercised under CI.** Our `tox` and GitHub Actions-based continuous integration (CI) configurations now both correctly exercise themselves against both PyPy 3.6 and 3.7, resolving the upstream actions/setup-python#171 issue for `beartype`. * **CI thresholded.** Our CI configuration now caps tests to a sane maximum duration of time to avoid a repeat of the pull request we do *not* talk about here. Okay, it was #23. I blame only myself. * **New functional tests,** including: * A **CPython-specific [mypy](http://mypy-lang.org) functional test,** optionally exercising our conformance to static type-checking standards when the third-party `mypy` package is installed under CPython. This test is sufficiently critical that we perform it under our CI workflow, guaranteeing test failures on any push or PR violating mypy expectations. * A **`README.rst` functional test,** optionally exercising the syntactic validity of our front-facing `README.rst` documentation when the third-party `docutils` package (i.e., the reference reST parser) is installed. This test is sufficiently expensive that we currently avoid performing it under our CI workflow. * **New unit tests,** including: * **Text munging unit tests,** exercising the private `beartype._util.text.utiltextmunge` submodule with lavish attention to regex-based fuzzy testing of the critical `number_lines()` function. Humble `git log` shout outs go out to @Heliotrop3 for this mythic changeset that warps the fragile fabric of the GitHub cloud to its own pellucid yet paradoxically impenetrable intentions, resolving issue #24. ## Documentation Revised * **Sphinx skeleton.** The `beartype` repository now defines a largely unpopulated skeleton for Sphinx-generated documentation formatted as reST and typically converted to HTML to be hosted at [Read The Docs (RTD)](https://beartype.readthedocs.io/en/latest/?badge=latest), generously contributed by @felix-hilden, Finnish computer vision expert and our first contributor! This skeleton enables: * An HTTP 404 redirect page on missing page hits. * The standard retinue of builtin Sphinx extensions (e.g., `autodoc`, `viewcode`). * MathJax configured for remote implicit downloads. * Napolean configured for NumPy-formatted docstrings. * An optional dependency on `sphinx_rtd_theme`, a third-party Sphinx extension providing RTD's official Sphinx HTML. * A **badge** (i.e., shield, status icon) on our front-facing `README.rst` documentation signifying the success of the most recent attempt to build and host this skeleton at RTD. * A **top-level `sphinx` script**, building Sphinx-based package documentation when manually run from the command line by interactive developers. * A **[beautiful banner graphic that makes grown adults openly weep](https://github.com/beartype/beartype-assets/tree/main/banner),** featuring the official `beartype` mascot "Mr. Nectar Palm" – again courtesy @felix-hilden, because sleep is for the weak and Felix has never known the word. * A **new prefacing "tl;dr" section** that's redundant with numerous other sections, but we're surprisingly okay with that. * A **new "Usage" section** that accidentally became a new tutorial and division of the existing "Overview" section into various subsections highlighting tradeoffs between `beartype` and existing type checkers, resolving clarity concerns raised by @kevinjacobs-progenity at issue #7. Thanks for the invaluable commentary, Kevin! * A **new "Frequently Asked Questions (FAQ)" section,** inspired by the admission from several prospective users that they have utterly no idea what @leycec is talking about. Fair play, users. You win this round. * A **new "Workflow" subsection of the "Developer" section,** listing developer-specific instructions for forking, cloning, installing, modifying, and submitting PRs for `beartype` in a live manner. * **Properly rendered code blocks,** kindly reported by humane human extraordinaire @harens in discussion topic #28. Thanks and may the little-seen English sun eternally shine upon ye, Haren! ## API Changed * Added: * `beartype.cave.CallableCTypes`. * `beartype.cave.HintGenericSubscriptedType`. * Deprecated: * `beartype.cave.HintPep585Type`. (*Exogenous exhaustion!*)
Beartype 0.6.0 has been released to a fanfare of cat mewls, crackling icicles, and the quietude of a snow-blanketed Canadian winter. ☃️ We hope you are warm and safe, coding as only you can code. This release brings explicit support for Compatibility Improved
Compatibility Broken
Packaging Improved
Features Added
Features Optimized
Features Deprecated
Issues Resolved
Tests Improved
Documentation Revised
API Changed
|
Hello! I love this project! Given that you plan to target tensor/array-likes post The most concrete thing to come out of this so far is PEP 646: Variadic Generics (still under review, but seems like it will go the distance). I hope that this thread was the appropriate place to post this. I will happily post elsewhere (or nowhere) if it is not. |
Oh! It's my favourite Ryan! This may sound doubtful, but I fondly remember when you starred @beartype. We were just starting to explode (in the best possible way) at that point, and then somebody from MIT starred us. Insert {oh-face-here}. Thanks so much for the heads up on PEP 646, too. I couldn't help myself and just gave my hot take on the mailing list. I'm afraid the knives may have come out. Let's see if my grim yet constructive commentary makes it through moderation. It's popcorn time. Sadly, PEP 646 got the Guido support, so it's more-or-less fait accompli. That's fine... I guess. Whatever PEP eventually lands, we'll wholeheartedly endorse and support it. It just would've been nice to get something a bit more practical, forward-compatible, and immediately useful. Thanks again, Ryan! We have thrilling changes in store for everyone just around the corner. Yup: it's our own homebrew tensor validation API. 😁 |
Well shucks, I am glad we were able to meet then! It has been exciting to see this project grow in popularity, and I am glad that I was able help create a fond memory 😄 And I'll be sure to stay tuned for your tensor-validation API! |
Uncork that expensive and only mildly carcinogenic champagne bottle you've been saving up for special data science occasions, folks. @leycec has finally done something that users actually want. 🍾 🍾 🍾 Introducing... blast beat drumroll, please! beartype 0.7.0, featuring the world's first PEP-compliant validation API. It's also:
Tensor Validation: Now Trending on BeartypeBut rather than mansplain at you all summer evening, I'll just leave this example bombshell here:
Here's the soft landing. Create a beartype validator by:
As a fully PEP-compliant type hint, Tensor Validation: Make This Faster, Beartype!We're not done yet. The above example is mildly inefficient, because it calls your user-defined lambda function once for each call to a We can. This is beartype, so we can always do better. Behold, alternate syntax for semantically type-hinting the same data structure but avoiding any pure-Python callable calls and thus the fastest tensor validator that has ever existed:
Full-fat O(n) Type-checking: Game Over, Man!We're still not done yet, even though the sweat is now pouring down your forehead, your five-year old is squalling something about vomit unicorns, and your wife is giving you the frown look that portends a troubled bedtime. Because beartype validators are Turing-complete, you can do literally anything with them – including things @leycec doesn't want you to like full-fat
In beartype validators, @leycec created his own arch-nemesis: @NegaLeycec. When @leycec made a public API so powerful it had the latent energy to destroy his own online legacy, only you can step away from the brink and do the right thing. Because data scientists don't let data scientists type-check in Obligatory Shoutouts@Heliotrop3, @HETHAT, @MartinPdeS, @Saphyel, @aiporre, @albanie, @djhenderson, @eehusky, @felix-hilden, @harens, @rsokl, @terrdavis, @thanakijwanavit: Beartype now does everything everyone wanted. Take all my GitHub karma and may all your Git commits foreevermore be blessed by passing unit tests. |
DAYUM! Congrats 😄 Excited to try this out! |
For all those eagerly awaiting to try the new version... MacPorts: macports/macports-ports@7c42a28 (Project page)
Pre-built binaries are available for macOS Big Sur all the way down to Snow Leopard which was released back in 2009. Yes, you did read that correctly. Yes, MacPorts is indeed better than Homebrew :) Homebrew: beartype/homebrew-beartype@eb0d97f
Binaries are available for macOS Big Sur, Catalina, and Linux (all x86_64). As always, thank you @leycec (and the other contributors) for your hard work on this amazing project! |
So. The prophesied time has finally come. My wife must now resurrect the Core 2 Duo 2010 MacBook from its plush velvet cryogenic storage chamber in our Hall of Ancient and Venerable Laptops. ...yup, the bedroom closet @harens has made you useful again, 2010 MacBook. 🤗
dem is fightin wurds Eternal gratitude, endless summer vibrations, and post-life positive karma to Haren for his tireless dedication! Wut a gr8 m8. |
Beartype 0.8.0 just dropped – and you were there to see it:
When your children ask about this moment, tell them they wouldn't believe the things you've seen online. Yes, things like:
What we're saying is: BadgeBeartype also now sports an official badge (i.e., shield intended for display by downstream projects): You may now proudly confirm your commitment to littering your front page with an incomprehensible smear of project shields using this reST- and Markdown-friendly text. It looks resplendent on your Git[Hub|Lab] repo, does it not? It does. Unsurprisingly, @posita made that... because what doesn't @posita do? Nuthin'! I'm pretty sure that means @posita does everything. tl;dr:
Everything is a go. Go, go, go! Do great science and engineering, great people. |
Badge spotted in the wild! https://pypi.org/project/dycelib/ 😉 |
Yes! It's happening. The badge is spreading like a slime mold before our very eyes. Relatedly: everyone who routinely rolls dice (...squints eyes at tabletop role-players, board gamers, and compulsive gamblers), check out @posita's spicy
🌶️ 🌶️ 🌶️ |
Beartype 0.9.0 has landed with a soft, mildly disconcerting gloop sound. $ pip install --upgrade beartype Coroutines! Asynchrous generators! Precision-agnostic All this (and more) is within your iron-fisted grasp. Will you shake the outstretched bear paw that only wants to dramatically improve the quality of your world-shattering codebase? Only your intrepid team of crack code commandos will decide the ultimate fate of... Python. 2021. Q4. |
Shout-outs to the based bear team: @KyleKing, @antonagestam, @Branii, @pbourke, and @posita. Your legend grows by the day. |
I love you beartype <3 |
$ pip install --upgrade beartype But first, this release is... In Homage to Dale PietakMy stunning Canadian mother-in-law fled the Soviet invasion of Lithuania... ...on horseback across the Lithuanian alps as an emaciated 5-year-old girl.
This release is for Dale. And now, a pivotal moment that could make you question every cherished assumption you once held dear but are now throwing away:
A Configuration API for the Ages (Go Figure)
# Import the configuration API.
from beartype import BeartypeConf, BeartypeStrategy
# Configure type-checking by passing @beartype an optional configuration.
@beartype(conf=BeartypeConf(
# Optionally switch to a different type-checking strategy, including:
# * "BeartypeStrategy.On", type-checking in O(n) linear time.
# (Currently unimplemented but roadmapped for a future release.)
# * "BeartypeStrategy.Ologn", type-checking in O(logn) logarithmic time.
# (Currently unimplemented but roadmapped for a future release.)
# * "BeartypeStrategy.O1", type-checking in O(1) constant time. This
# default strategy need *NOT* be explicitly enabled.
# * "strategy=BeartypeStrategy.O0", disabling type-checking entirely.
strategy=BeartypeStrategy.On,
# Optionally enable developer-friendly debugging for this decoration.
is_debug=True,
))
def my_configured_function(
# Parameter type-checked in O(n) linear time. (Currently unimplemented.)
param_checked_in_On_time: list[int],
# Return type-checked in O(n) linear time, too. (Currently unimplemented.)
) -> set[str]:
return set(str(item) for item in param_checked_in_On_time) So, it happened. Secondly, it happened efficiently. Beartype configurations are self-memoizing: >>> BeartypeConf() is BeartypeConf()
True
>>> BeartypeConf(is_debug=True) is BeartypeConf(is_debug=True)
True Moreover, beartype subdecorators returned by configuring >>> beartype(conf=BeartypeConf()) is beartype(conf=BeartypeConf())
True
>>> beartype(conf=BeartypeConf(is_debug=True)) is beartype(conf=BeartypeConf(is_debug=True))
True This means you don't need to alias, cache, or store anything. We did that for you, because @beartype never stopped believing in you. Of course, you can alias beartype subdecorators if you really must for readability: e.g., # I grow weary of repeating myself, @beartype.
beartype_On = beartype(conf=BeartypeConf(
strategy=BeartypeStrategy.On))
# This is the sound of sweet justice.
@beartype_On
def typecheck_slowly(this_gonna_hurt: list[int]) -> int:
return itertools.accumulate(this_gonna_hurt) Nothing Actually Works, Does it?As the above tl;dr implies, the
We'll gradually catch up to your codebase. When we do, you'll know because everything will grind to a halt and Twitter will erupt with outrage. What API Horrors Have You Unleashed Now!?To recap, the
Let's admit we're all exhausted at this point. Ideally, I'd be done here. Sadly, we're only just beginning. The 0.10.0 release cycle was a titanic struggle between the forces of API bloat and bloated apathy. API bloat won. Introducing... A Functional Type-checking API that Functions
# Import the functional API.
>>> from beartype import is_bearable, die_if_unbearable
# Test whether any object satisfies any type hint at any time.
>>> is_bearable(['Things', 'fall', 'apart;'], list[str])
True
>>> is_bearable(['the', 'centre', 'cannot', 'hold;'], list[int])
False
# Raise an exception if any object violates any type hint at any time.
>>> die_if_unbearable(['And', 'what', 'rough', 'beast,'], list[str])
>>> die_if_unbearable(['its', 'hour', 'come', 'round'], list[int])
beartype.roar.BeartypeAbbyHintViolation: Object ['its', 'hour', 'come',
'round'] violates type hint list[int], as list index 0 item 'its' not
instance of int. 😮
To recap:
To sweeten the sour deal, all of the above also accept an optional # In theory, this type-checks the entire list in linear time.
# In practice, we haven't actually implemented that yet.
# That's @beartype for you: it promises much,
# implements some, and documents even less.
is_bearable(
obj=['Things', 'fall', 'apart;'],
hint=list[str],
conf=BeartypeConf(BeartypeStrategy.On),
) Even the 12 year-old child prodigy from Switzerland who's reading this against the express wishes of her overprotective nursemaid is exhausted by now. Nonetheless, the trainwreck of API inevitability continues. Introducing... A Compatibility API that Avoids Breaking the FutureIf you @beartype under Python < 3.9, you've taken an arrow to the knee from our unfairly maligned PEP 585 deprecations: /home/kumamon/beartype/_util/hint/pep/utilpeptest.py:377:
BeartypeDecorHintPep585DeprecationWarning: PEP 484 type hint
typing.List[int] deprecated by PEP 585 scheduled for removal in the first
Python version released after October 5th, 2025. To resolve this, import
this hint from "beartype.typing" rather than "typing". See this discussion
for further details and alternatives:
https://github.com/beartype/beartype#pep-585-deprecations Previously, you had to do something about that. Now, let @beartype do everything for you by globally replacing all imports from the standard # Just do this...
from beartype import typing
# ...instead of this.
#import typing
# Likewise, just do this...
from beartype.typing import Dict, FrozenSet, List, Set, Tuple, Type
# ...instead of this.
#from typing import Dict, FrozenSet, List, Set, Tuple, Type The public
Currently, that's all
The endless wheel of suffering continues as...
|
Shoutouts to the Burgeoning Bear Clan: @posita, @TeamSpen210, @paulhutchings, @qiujiangkun, @MrHemlock, @matanster, @mikaelho, @Masoudas, @mvaled, @mxlei01, @harshita-gupta, @shawwn, @NiklasRosenstein, @dycw, and @Jasha10. |
$ pip install --upgrade beartype So much typing goodness is in store for your codebase. Play the highlight real, boys! Alternately, read the infodump and weep for your free time. Colour: Beyond MonochromeIs that... No, but it couldn't be. Yes, but it is! It's thematically appropriate and aesthetically pleasing ANSII pigmentation in type-checking violations raised by @beartype: Colour me impressed, @beartype. For safety, colourization only happens conditionally when standard output is attached to an interactive terminal. Let us know if you detest this scheme. B-b-but... how could you? It's beautiful. Praise be to machine learning guru @justinchuby (Justin Chu) for his outstanding volunteerism in single-handedly making all this juiciness happen at PR #162. 😮 pyright: @beartype No Longer Disagrees with You@beartype now officially supports two static type-checkers:
VSCode's rampant popularity makes PyLance and thus pyright the new ad-hoc standard for Python typing. Microsoft's will be done. I'm pretty certain – but not certain – that @beartype is the first "large" Python project to support multiple competing static type-checkers. We lack common sense. Unofficially, we don't advise doing this at home. The @beartype codebase is now a festering litter dump of Class Decoration: Save Your Wrists with @beartypeRejoice, RSI-wracked buddies! @beartype now decorates classes, too – including non-trivial nested classes with self-referential annotations postponed under PEP 563, just 'cause: from __future__ import annotations # <-- never actually do this
from beartype import beartype
# One @beartype decoration to rule them all and in /dev/null bind them.
@beartype
class ImAClassWouldILie(object):
def this_is_fine(self) -> ImAClassWouldILie:
return self
@staticmethod
def static_methods_are_fine_too() -> ImAClassWouldILie:
return ImAClassWouldILie()
@classmethod
def yup_class_methods_also_fine(cls) -> ImAClassWouldILie:
return cls()
@property
def omg_property_methods_are_fine(self) -> ImAClassWouldILie:
return self
class ImANestedClassYouCanTellBecauseImNested(object):
def wtf_beartype_how_is_this_fine(self) -> ImAClassWouldILie:
return ImAClassWouldILie()
def still_cant_believe_this_is_fine_either(self) -> (
ImANestedClassYouCanTellBecauseImNested):
return self Say goodbye to decorating methods manually. Your wrists that are throbbing with pain will thank you. But this isn't simply a nicety. If your codebase is object-oriented (...please Guido, let it be so), let us now suggest that you incrementally refactor your existing usage of @beartype from methods to classes. Why? Because:
beartype.door: The Decidedly Object-oriented Runtime-checkerOh, boy. Now we hittin' the Hard Stuff™. Has anyone ever tried to actually use type hints at runtime? Like, not merely annotate classes, callables, and attributes with type hints but actually use those type hints for a productive purpose? Anybody? Anybody? ...helllllllllllllo? Fret not, bear bros. @beartype now enables anyone to introspect, query, sort, compare, type-check, or otherwise manhandle type hints at any time in constant time. Dare your codebase open... the DOOR (Decidedly Object-oriented Runtime-checker)? spooky Halloween sounds # This is DOOR. It's a Pythonic API providing an object-oriented interface
# to low-level type hints that basically have no interface whatsoever.
>>> from beartype.door import TypeHint
>>> union_hint = TypeHint(int | str | None)
>>> print(union_hint)
TypeHint(int | str | None)
# DOOR hints have Pythonic classes -- unlike normal type hints.
>>> type(union_hint)
beartype.door.UnionTypeHint # <-- what madness is this?
# DOOR hints can be classified Pythonically -- unlike normal type hints.
>>> from beartype.door import UnionTypeHint
>>> isinstance(union_hint, UnionTypeHint) # <-- *shocked face*
True
# DOOR hints can be type-checked Pythonically -- unlike normal type hints.
>>> union_hint.is_bearable('The unbearable lightness of type-checking.')
True
>>> union_hint.die_if_unbearable(b'The @beartype that cannot be named.')
beartype.roar.BeartypeDoorHintViolation: Object b'The @beartype that cannot
be named.' violates type hint int | str | None, as bytes b'The @beartype
that cannot be named.' not str, <class "builtins.NoneType">, or int.
# DOOR hints can be iterated Pythonically -- unlike normal type hints.
>>> for child_hint in union_hint: print(child_hint)
TypeHint(<class 'int'>)
TypeHint(<class 'str'>)
TypeHint(<class 'NoneType'>)
# DOOR hints can be indexed Pythonically -- unlike normal type hints.
>>> union_hint[0]
TypeHint(<class 'int'>)
>>> union_hint[-1]
TypeHint(<class 'str'>)
# DOOR hints can be sliced Pythonically -- unlike normal type hints.
>>> union_hint[0:2]
(TypeHint(<class 'int'>), TypeHint(<class 'str'>))
# DOOR hints supports "in" Pythonically -- unlike normal type hints.
>>> TypeHint(int) in union_hint # <-- it's all true.
True
>>> TypeHint(bool) in union_hint # <-- believe it.
False
# DOOR hints are sized Pythonically -- unlike normal type hints.
>>> len(union_hint) # <-- woah.
3
# DOOR hints reduce to booleans Pythonically -- unlike normal type hints.
>>> if union_hint: print('This type hint has children.')
This type hint has children.
>>> if not TypeHint(tuple[()]): print('But this other type hint is empty.')
But this other type hint is empty.
# DOOR hints support equality Pythonically -- unlike normal type hints.
>>> from typing import Union
>>> union_hint == TypeHint(Union[int, str, None])
True # <-- this is madness.
# DOOR hints support comparisons Pythonically -- unlike normal type hints.
>>> union_hint <= TypeHint(int | str | bool | None)
True # <-- madness continues.
# DOOR hints are semantically self-caching.
>>> TypeHint(int | str | bool | None) is TypeHint(None | bool | str | int)
True # <-- blowing minds over here. Praise be to Harvard microscopist and lead @napari dev @tlambert03 (Talley Lambert) for his phenomenal volunteerism in single-handedly building a new typing world. He ran the labyrinthian gauntlet of many, many painful PRs so that you didn't have to.
beartype.peps: This Day All PEP 563 DiesSo. Fret not even more, bear bros. @beartype now enables anyone to resolve PEP 563-postponed type hints without actually needing to sleep on a bed of nails with this GitHub issue as your only comfort: # In "horrorshow.py":
from __future__ import annotations # <-- never actually do this
def sad_function_is_sad() -> str | bytes | None:
if 'sad':
return "You can't spell 'sadness' without madness (excluding the 'm')."
else:
return b'This cannot be! But it is! An unreachable condition erupts.'
print('Uh oh. We got postponed type hints here:')
print(repr(sad_function_is_sad.__annotations__['return']))
print()
# Your time is over, PEP 563.
from beartype.peps import resolve_pep563
# Make the bad postponed type hints go away, Bear Daddy.
resolve_pep563(sad_function_is_sad)
print('Suck it, PEP 563. Only real type hints need apply:')
print(repr(sad_function_is_sad.__annotations__['return'])) Now run that, because you trust @leycec implicitly. You do trust @leycec implicitly, don't you? 🥲
Lastly but not leastly... ...to financially feed @leycec and his friendly @beartype through our new GitHub Sponsors profile. Come for the candid insider photos of a sordid and disreputable life in the Canadian interior; stay for the GitHub badge and warm feelings of general goodwill. Cue hypnagogic rave music that encourages fiscal irresponsibility. Shoutouts to the Beasts in BackGreets to the bear homies: @tlambert03, @justinchuby, @rskol, @langfield, @posita, @braniii, @dosisod, @bitranox, @jdogburck, @da-geek-incite, @MaxSchoenau, @gelatinouscube42, @stevemarin, @rbroderi, @kloczek, @twoertwein, @wesselb, and @patrick-kidger. 🏩 |
Thanks @leycec! @@ -21,7 +21,7 @@
class ImANestedClassYouCanTellBecauseImNested(object):
def wtf_beartype_how_is_this_fine(self) -> ImAClassWouldILie:
- return ImAClassWouldILie
+ return ImAClassWouldILie()
def still_cant_believe_this_is_fine_either(self) -> (
ImANestedClassYouCanTellBecauseImNested): |
@Jasha10: That's a teaching moment. Never trust a class named I jest, of course. Thanks so much for the fast fix! I've patched up the original example, which should now lie a bit less than it did before. |
Big bear hugs to machine learning paragons @KyleKing and @albanie (Samuel Albanie) for their incredibly generous support through GitHub Sponsors. By your charity, hard things become easier. |
$ pip install --upgrade beartype We've got configuration and exception APIs so well-documented you won't believe it's @beartype! All these seductive lies and more are yours for only the low price of your vanishing time, invaluable attention, and lifetime of regret. Reading this will only take five minutes. Okay, maybe ten. Fifty at the most. Surely, no more than a full day. Alternately, read the lore dump and cry as an unedited wave of raw plaintext assaults you. But First, Soothing AI Art that Obsoletes All Human EffortCancerous mandelbulb bears? You gazed upon it in befuddlement here first, I'm pretty sure: Brought to you by some AI somewhere. Alright, alright! It's Midjourney v4. Yup. We shillin'. Clearly, Midjourney is internally using @beartype. How else can one explain the pristine beauty one witnesses above? One can't. Clearly. This paragraph is one big sprawling lie. After you successfully "read" each of the following sections, GitHub will drip-feed you another cancerous mandelbub bear from beyond the wall of sleep. That's right. We're gamifying this with a dopamine feedback cycle encouraging addictive and self-destructive behaviour. Let's begin. Configuration: Control @beartype, Because You Know BetterIf I had a toonie Canada: incoming for every time someone loudly hated the glaring kaleidoscope of colour Wait. Where am I? Who am I? Oh, right. I am @leycec and this is @beartype. Thankfully, # Import the requisite machinery.
from beartype import beartype, BeartypeConf
# Dynamically create a new @monobeartype decorator disabling colour.
monobeartype = beartype(conf=BeartypeConf(is_color=False))
# Decorate with this decorator rather than @beartype everywhere.
@monobeartype
def muh_colorless_func() -> str:
return b'In the kingdom of the blind, you are now king.' Cancerous mandelbulb bear. The good times won't stop there, however much you might want them to. PEP 484's Implicit Numeric Tower: It's BaaaackAll two of you that have read the Black Bible of Typing (AKA PEP 484), please raise your paws. Okay. It's just me, isn't it? I'll pretend there are at least two of you to make myself feel better. PEP 484 contains this suspiciously opinionated section I dub the implicit numeric tower. And I quote Guido:
When you find yourself embedding ASCII emoji like ":-)" in public standards, you know that you have done wrong and should now abase yourself with a televised ablution involving skintight burlap sacks and burly leather switches. Instead, that unsubstantiated drivel actually got published! I sigh. Thankfully, # Import the requisite machinery.
from beartype import beartype, BeartypeConf
# Dynamically create a new @beartowertype decorator enabling the tower.
beartowertype = beartype(conf=BeartypeConf(is_pep484_tower=True))
# Decorate with this decorator rather than @beartype everywhere.
@beartowertype
def crunch_numbers(numbers: list[float]) -> float:
return sum(numbers)
# This is now fine.
crunch_numbers([3, 1, 4, 1, 5, 9])
# This is still fine, too.
crunch_numbers([3.1, 4.1, 5.9]) Cancerous mandelbulb bear. @beartype: Is It Actually Doing Anything At All, Really?Have you ever wondered whether @beartype might just be pretending to do something... while in actuality doing nothing whatsoever? You are not alone. I too wonder this. Thankfully, # Import the requisite machinery.
>>> from beartype import beartype, BeartypeConf
# Dynamically create a new @bugbeartype decorator enabling debugging.
# Insider D&D jokes in my @beartype? You'd better believe. It's happening.
>>> bugbeartype = beartype(conf=BeartypeConf(is_debug=True))
# Decorate with this decorator rather than @beartype everywhere.
>>> @bugbeartype
... def muh_bugged_func() -> str:
... return b'Consistency is the bugbear that frightens little minds.'
(line 0001) def muh_bugged_func(
(line 0002) *args,
(line 0003) __beartype_func=__beartype_func, # is <function muh_bugged_func at 0x7f52733bad40>
(line 0004) __beartype_conf=__beartype_conf, # is "BeartypeConf(is_color=None, is_debug=True, is_pep484_tower=False, strategy=<BeartypeStrategy...
(line 0005) __beartype_get_violation=__beartype_get_violation, # is <function get_beartype_violation at 0x7f5273081d80>
(line 0006) **kwargs
(line 0007) ):
(line 0008) # Call this function with all passed parameters and localize the value
(line 0009) # returned from this call.
(line 0010) __beartype_pith_0 = __beartype_func(*args, **kwargs)
(line 0011)
(line 0012) # Noop required to artificially increase indentation level. Note that
(line 0013) # CPython implicitly optimizes this conditional away. Isn't that nice?
(line 0014) if True:
(line 0015) # Type-check this passed parameter or return value against this
(line 0016) # PEP-compliant type hint.
(line 0017) if not isinstance(__beartype_pith_0, str):
(line 0018) raise __beartype_get_violation(
(line 0019) func=__beartype_func,
(line 0020) conf=__beartype_conf,
(line 0021) pith_name='return',
(line 0022) pith_value=__beartype_pith_0,
(line 0023) )
(line 0024)
(line 0025) return __beartype_pith_0 Hah! @beartype is doing something. You still don't believe me, but that debug output doesn't lie. ...or does it Cancerous mandelbulb bear. @beartype: Wait, What Violated a Type-check?Have you ever read a riotously coloured @beartype exception message and thought to yourself: "Tell me what you know, @beartype! Which object is this that's violating my codebase? Which object, curse your clumsy paws!?" Exception messages like this are less helpful than desired:
Right? Like, that's not getting anyone very far. @beartype, please do better. Thankfully, # Import the requisite machinery.
from beartype import beartype
from beartype.roar import BeartypeCallHintViolation
# Arbitrary user-defined classes.
class SpiritBearIGiveYouSalmonToGoAway(object): pass
class SpiritBearIGiftYouHoneyNotToStay(object): pass
# Arbitrary instance of one of these classes.
SPIRIT_BEAR_REFUSE_TO_GO_AWAY = SpiritBearIGiftYouHoneyNotToStay()
# Callable annotated to accept instances of the *OTHER* class.
@beartype
def when_spirit_bear_hibernates_in_your_bed(
best_bear_den: SpiritBearIGiveYouSalmonToGoAway) -> None: pass
# Call this callable with this invalid instance.
try:
when_spirit_bear_hibernates_in_your_bed(
SPIRIT_BEAR_REFUSE_TO_GO_AWAY)
# *MAGIC HAPPENS HERE*. Catch violations and inspect their "culprits"!
except BeartypeCallHintViolation as violation:
# Assert that one culprit was responsible for this violation.
assert len(violation.culprits) == 1
# The one culprit: don't think we don't see you hiding there!
culprit = violation.culprits[0]
# Assert that this culprit is the same instance passed above.
assert culprit is SPIRIT_BEAR_REFUSE_TO_GO_AWAY The perspicacious reader ...yes, we know that word, too will note that # Import the requisite machinery.
from beartype import beartype
from beartype.roar import BeartypeCallHintViolation
from beartype.typing import List
# Callable annotated to accept a standard container.
@beartype
def we_are_all_spirit_bear(
best_bear_dens: List[List[str]]) -> None: pass
# Standard container deeply violating the above type hint.
SPIRIT_BEAR_DO_AS_HE_LIKE = [
[b'Why do you sleep in my pinball room, Spirit Bear?']]
# Call this callable with this invalid container.
try:
we_are_all_spirit_bear(SPIRIT_BEAR_DO_AS_HE_LIKE)
# Shoddy magic happens here. Catch violations and try (but fail) to
# inspect the original culprits, because they were containers!
except BeartypeCallHintViolation as violation:
# Assert that two culprits were responsible for this violation.
assert len(violation.culprits) == 2
# Root and leaf culprits. We just made these words up, people.
root_culprit = violation.culprits[0]
leaf_culprit = violation.culprits[1]
# Assert that these culprits are, in fact, just repr() strings.
assert root_culprit == repr(SPIRIT_BEAR_DO_AS_HE_LIKE)
assert leaf_culprit == repr(SPIRIT_BEAR_DO_AS_HE_LIKE[0][0]) Don't ask why @beartype is only giving you Cancerous mandelbulb bear.
|
Damn that's a chonky feature list - thank you for all your efforts on this project! Happy to say our company @zeroguard is now officially a beartype sponsor. And let's be real, It would be almost criminal for me not to sponsor, given that 98% of our Python file coverage on our 400-ish internal repos shows |
@leycec <https://github.com/leycec>,
we have got here some migrating bears (we mostly call them "Problembär")
in austria.
They are coming from germany, czech, slovenia, slovakia and other
neighbouring countries -
usually we shoot them, but I will give this particular canadian bear
definitely a shot.
yours sincerely
Robert, Vienna, Austria aka bitranox
Der ausgestopfte Braunbär "Bruno" im Museum Mensch und Natur in München
Am 18.01.2023 um 08:50 schrieb Cecil Curry:
…
|beartype 0.12.0| gently descends like an AI-upscaled snowflake to
PyPI <https://pypi.org/project/beartype>, where goodness lives:
$ pip install --upgrade beartype
We've got *configuration*
<https://github.com/beartype/beartype#beartype-configuration> and
*exception APIs*
<https://github.com/beartype/beartype#beartype-exceptions> so
well-documented you won't believe it's @beartype
<https://github.com/beartype>! All these seductive lies and more are
yours for only the low price of your vanishing time, invaluable
attention, and lifetime of regret. Reading this will only take five
minutes. Okay, maybe ten. Fifty at the most. Surely, no more than a
full day.
Alternately, read the lore dump
<https://github.com/beartype/beartype/releases/tag/v0.12.0> and cry as
an unedited wave of raw plaintext assaults you.
But First, Soothing AI Art that Obsoletes All Human Effort
Cancerous mandelbulb bears? You gazed upon it in befuddlement here
first, I'm pretty sure:
image
<https://user-images.githubusercontent.com/217028/213112470-6a86c68c-05c5-4c42-a905-1ba55f84c2fa.png>
_Brought to you by some AI somewhere. Alright, alright! It's
Midjourney v4 <https://midjourney.com>. Yup. We shillin'.
Clearly, Midjourney is internally using @beartype
<https://github.com/beartype>. How else can one explain the pristine
beauty one witnesses above? One can't. Clearly. ^This paragraph is one
big sprawling lie.
After you successfully "read" each of the following sections, GitHub
will drip-feed you another stained glass window bear. That's right.
We're gamifying this with a dopamine feedback cycle encouraging
addictive and self-destructive behaviour. Let's begin.
Configuration: Control @beartype <https://github.com/beartype>,
Because You Know Better
If I had a toonie <https://en.wikipedia.org/wiki/Toonie> ^/Canada:
incoming/ for every time someone loudly hated the glaring kaleidoscope
of colour |beartype 0.11.0| decorates type-checking violations with
(without your permission), I'd be guzzling apple cider in Cancún. I
can almost taste that sweet nectar, full-bodied yet surprisingly
non-alcoholic. I can almost bask in the warm offshore breeze. The Sun
herself beams with joy. /Ahhhhhhh.../
Wait. Where am I? Who am I? Oh, right. I am @leycec
<https://github.com/leycec> and this is @beartype
<https://github.com/beartype>. |</sigh>|
Thankfully, |beartype 0.12.0| has got your hairy back. Return to the
monochrome days of yore, where things were better and your eyeballs
didn't bleed
<https://github.com/beartype/beartype#beartypeconf-is-color>. Bask in
the applause of your coworkers as you loudly take all this credit (and
more):
# Import the requisite machinery.
from beartype import beartype,BeartypeConf
# Dynamically create a new @monobeartype decorator disabling colour.
monobeartype = beartype(conf=BeartypeConf(is_color=False))
# Decorate with this decorator rather than @beartype everywhere.
@monobeartype
def muh_colorless_func()-> str:
return b'In the kingdom of the blind, you are now king.'
Cancerous mandelbulb bear.
image
<https://user-images.githubusercontent.com/217028/213113214-4d92af34-b011-42cf-915c-955b297ebef6.png>
The good times won't stop there, however much you might want them to.
|beartype 0.12.0| also gives configuration options for...
PEP 484's Implicit Numeric Tower: It's /Baaaack/
All two of you that have read the Black Bible of Typing (AKA PEP 484
<https://peps.python.org/pep-0484/>), you may now raise your paws.
Okay. It's just me, isn't it? I'll pretend there are at least two of
you to make myself feel better.
PEP 484 contains this suspiciously opinionated section I dub the
*implicit numeric tower
<https://peps.python.org/pep-0484/#the-numeric-tower>*. And I quote Guido:
PEP 3141 <https://peps.python.org/pep-3141> defines Python’s
numeric tower, and the |stdlib| module numbers implements the
corresponding ABCs (|Number|, |Complex|, |Real|, |Rational| and
|Integral|). There are some issues with these ABCs, but the
built-in concrete numeric classes |complex|, |float| and |int| are
ubiquitous (especially the latter two :-).
Rather than requiring that users write import numbers and then use
|numbers.Float| etc., this PEP proposes a straightforward shortcut
that is almost as effective: when an argument is annotated as
having type |float|, an argument of type |int| is acceptable;
similar, for an argument annotated as having type |complex|,
arguments of type |float| or |int| are acceptable. This does not
handle classes implementing the corresponding ABCs or the
|fractions.Fraction| class, but we believe those use cases are
exceedingly rare.
When you find yourself embedding ASCII emoji like ":-)" in public
standards, you know that you have done wrong and should now abase
yourself with a televised ablution involving skintight burlap sacks
and burly leather switches. Instead, that unsubstantiated drivel
actually got /published!/ I sigh.
Thankfully, |beartype 0.12.0| has got your hairy back... again. You
could just write |int | float| rather than do anything Guido says. Or
you could ignore sound advice and return to the bad old days of PEP
484, where writing |float| when you /really/ mean |int | float| saves
you from a vicious downward spiral of carpal tunnel syndrome and
subsequent occupational bankruptcy
<https://github.com/beartype/beartype#beartypeconf-is-pep484-tower>.
You ask and you receive:
# Import the requisite machinery.
from beartype import beartype,BeartypeConf
# Dynamically create a new @beartowertype decorator enabling the tower.
beartowertype = beartype(conf=BeartypeConf(is_pep484_tower=False))
# Decorate with this decorator rather than @beartype everywhere.
@beartowertype
def crunch_numbers(numbers:list[float])-> float:
return sum(numbers)
# This is now fine.
crunch_numbers([3,1,4,1,5,9])
# This is still fine, too.
crunch_numbers([3.1,4.1,5.9])
Cancerous mandelbulb bear.
image
<https://user-images.githubusercontent.com/217028/213113473-db5931e3-7c8c-4d74-9dee-19691ed16886.png>
@beartype <https://github.com/beartype>: Is It Actually Doing
Anything At All, Really?
Have you ever wondered whether @beartype <https://github.com/beartype>
might just be /pretending/ to do something... while in /actuality/
doing nothing whatsoever? You are not alone. I too wonder this.
Thankfully, |beartype 0.12.0| has got your hairy back... again and
again. Force @beartype to show you what it's hiding behind your hairy
back by printing out the type-checking code it dynamically generates
on your behalf
<https://github.com/beartype/beartype#beartypeconf-is-debug>:
# Import the requisite machinery.
>>> from beartype import beartype,BeartypeConf
# Dynamically create a new @bugbeartype decorator enabling debugging.
# Insider D&D jokes in my @beartype? You'd better believe. It's happening.
>>> bugbeartype = beartype(conf=BeartypeConf(is_debug=True))
# Decorate with this decorator rather than @beartype everywhere.
>>> @bugbeartype
...def muh_bugged_func()-> str:
...return b'Consistency is the bugbear that frightens little minds.'
(line 0001)def muh_bugged_func(
(line 0002)*args,
(line 0003)__beartype_func=__beartype_func,# is <function muh_bugged_func at 0x7f52733bad40>
(line 0004)__beartype_conf=__beartype_conf,# is "BeartypeConf(is_color=None, is_debug=True,
is_pep484_tower=False, strategy=<BeartypeStrategy...
(line 0005)__beartype_get_violation=__beartype_get_violation,# is <function get_beartype_violation at 0x7f5273081d80>
(line 0006)**kwargs
(line 0007) ):
(line 0008)# Call this function with all passed parameters and localize the value
(line 0009)# returned from this call.
(line 0010)__beartype_pith_0 = __beartype_func(*args,**kwargs)
(line 0011)
(line 0012)# Noop required to artificially increase indentation level. Note that
(line 0013)# CPython implicitly optimizes this conditional away. Isn't that nice?
(line 0014)if True:
(line 0015)# Type-check this passed parameter or return value against this
(line 0016)# PEP-compliant type hint.
(line 0017)if not isinstance(__beartype_pith_0,str):
(line 0018)raise __beartype_get_violation(
(line 0019)func=__beartype_func,
(line 0020)conf=__beartype_conf,
(line 0021)pith_name='return',
(line 0022)pith_value=__beartype_pith_0,
(line 0023) )
(line 0024)
(line 0025)return __beartype_pith_0
Hah! @beartype <https://github.com/beartype> /is/ doing something. You
still don't believe me, but that debug output doesn't lie. ^...or does it
Cancerous mandelbulb bear.
image
<https://user-images.githubusercontent.com/217028/213113599-685a74fe-97a8-4f19-b633-4e063d5e2629.png>
@beartype <https://github.com/beartype>: Wait, /What/ Violated a
Type-check?
Have you ever read a riotously coloured @beartype
<https://github.com/beartype> exception message and thought to
yourself: "Tell me what you know, @beartype
<https://github.com/beartype>! Which object is this that's violating
my codebase? Which object, curse your clumsy paws!?"
Exception messages like this are less helpful than desired:
|beartype.roar.BeartypeCallHintParamViolation: @beartyped
my_module.MyClass.__init__() parameter my_array="array([[False,
False], [False, False], [ True, False], [ True, False], ... violates
type hint NArray[Shape['11 foo'], bool], as <protocol "numpy.ndarray">
"array([[False, False], [False, False], [ True, False], [ True,
False], ... not instance of <class "nptyping.common.NArray[Shape['11
foo'], bool]">. |
Right? Like, that's not getting anyone very far. @beartype
<https://github.com/beartype>, please do better.
Thankfully, |beartype 0.12.0| heard your plaintive cries in the
darkness. By the power of the new |BeartypeCallHintViolation.culprits|
property defined by all type-checking violation exceptions
<https://github.com/beartype/beartype#BeartypeCallHintViolation-culprits>,
you too can dynamically inspect the /exact/ object responsible for
violating type-checking at runtime:
# Import the requisite machinery.
from beartype import beartype
from beartype.roar import BeartypeCallHintViolation
# Arbitrary user-defined classes.
class SpiritBearIGiveYouSalmonToGoAway(object):pass
class SpiritBearIGiftYouHoneyNotToStay(object):pass
# Arbitrary instance of one of these classes.
SPIRIT_BEAR_REFUSE_TO_GO_AWAY = SpiritBearIGiftYouHoneyNotToStay()
# Callable annotated to accept instances of the *OTHER* class.
@beartype
def when_spirit_bear_hibernates_in_your_bed(
best_bear_den:SpiritBearIGiveYouSalmonToGoAway)-> None:pass
# Call this callable with this invalid instance.
try:
when_spirit_bear_hibernates_in_your_bed(
SPIRIT_BEAR_REFUSE_TO_GO_AWAY)
# *MAGIC HAPPENS HERE*. Catch violations and inspect their "culprits"!
except BeartypeCallHintViolation as violation:
# Assert that one culprit was responsible for this violation.
assert len(violation.culprits)== 1
# The one culprit: don't think we don't see you hiding there!
culprit = violation.culprits[0]
# Assert that this culprit is the same instance passed above.
assert culprit is SPIRIT_BEAR_REFUSE_TO_GO_AWAY
The perspicacious reader ^...yes, we know /that/ word, too will note
that |BeartypeCallHintViolation.culprits| property is actually a
|tuple| of one or more items. Why? Python containers (e.g., |dict|,
|list|). If an item of a Python container is the responsible culprit,
then @beartype <https://github.com/beartype> doesn't just provide you
that item; it provides you both that item /and/ the container
containing that item.
# Import the requisite machinery.
from beartype import beartype
from beartype.roar import BeartypeCallHintViolation
from beartype.typing import List
# Callable annotated to accept a standard container.
@beartype
def we_are_all_spirit_bear(
best_bear_dens:List[List[str]])-> None:pass
# Standard container deeply violating the above type hint.
SPIRIT_BEAR_DO_AS_HE_LIKE = [
[b'Why do you sleep in my pinball room, Spirit Bear?']]
# Call this callable with this invalid container.
try:
we_are_all_spirit_bear(SPIRIT_BEAR_DO_AS_HE_LIKE)
# Shoddy magic happens here. Catch violations and try (but fail) to
# inspect the original culprits, because they were containers!
except BeartypeCallHintViolation as violation:
# Assert that two culprits were responsible for this violation.
assert len(violation.culprits)== 2
# Root and leaf culprits. We just made these words up, people.
root_culprit = violation.culprits[0]
leaf_culprit = violation.culprits[1]
# Assert that these culprits are, in fact, just repr() strings.
assert root_culprit == repr(SPIRIT_BEAR_DO_AS_HE_LIKE)
assert leaf_culprit == repr(SPIRIT_BEAR_DO_AS_HE_LIKE[0][0])
Don't ask why @beartype <https://github.com/beartype> is only giving
you |repr()| strings here. Just... don't. Debug like your life depends
on it – because it very well might, someday. ^/cue sweaty action scene/
Cancerous mandelbulb bear.
image
<https://user-images.githubusercontent.com/217028/213113734-feb0df87-41a2-42e7-834f-df05841b29cc.png>
|typing.NamedTuple|: Yeah, We Do That Too.
@beartype <https://github.com/beartype> now fully supports named
tuples. @langfield <https://github.com/langfield> demanded it.
@langfield <https://github.com/langfield> gets it!
# Import the requisite machinery.
from typing import NamedTuple
# Decorate named tuples with @beartype.
@beartype
class LangfieldStyleTuple(NamedTuple):
# Annotate fields with PEP-compliant type hints.
tremble_and_weep:str | bytes
Stained glass window bear.
Nuitka: Yeah, We Do That Too.
@beartype <https://github.com/beartype> now fully supports Nuitka
<https://github.com/Nuitka/Nuitka>, the Python-to-C[++] transpiler we
desperately hope ports itself to the Android toolchain. Make this
happen, Nuitka! Ain't nobody got time for Cython in 2023.
We know that @beartype <https://github.com/beartype> now fully
supports Nuitka, because integration tests in @beartype
<https://github.com/beartype>'s test suite says we do. Would tests
lie!?!? ^😮💨
Stained glass window bear.
Lastly but not leastly...
we doin' this
<https://raw.githubusercontent.com/beartype/beartype-assets/main/brand/brand-2600x800.png>
...to financially feed @leycec and his friendly @beartype through our
new GitHub Sponsors profile <https://github.com/sponsors/leycec>. Come
for the candid insider photos of a sordid and disreputable life in the
Canadian interior; stay for the GitHub badge and warm feelings of
general goodwill.
Cue hypnagogic rave music that encourages fiscal irresponsibility.
Shoutouts to the Beasts in Back
Greets to the bear homies: @Jasha10 <https://github.com/Jasha10>,
@foxx <https://github.com/foxx>, @tlambert03
<https://github.com/tlambert03>, @RSkol <https://github.com/RSkol>,
@langfield <https://github.com/langfield>, @posita
<https://github.com/posita>, @kloczek <https://github.com/kloczek>,
@wesselb <https://github.com/wesselb>, @yioda
<https://github.com/yioda>, @shenwpo <https://github.com/shenwpo>,
@ShaneHeldsinger <https://github.com/ShaneHeldsinger>, @murphyk
<https://github.com/murphyk>, @ArneBachmannDLR
<https://github.com/ArneBachmannDLR>, @jpetrucciani
<https://github.com/jpetrucciani>, and @pinkwah
<https://github.com/pinkwah>. 🏩
Cancerous mandelbulb bear.
image
<https://user-images.githubusercontent.com/217028/213113799-eb311e2f-07f0-464a-b86c-630497a4a0ea.png>
—
Reply to this email directly, view it on GitHub
<#7 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AB7X3J2CV466UMN63H5IBVLWS6OFBANCNFSM4UUTWABA>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
...munch, munch, munch. That is the sound of Let's start this ML party. $ pip install --upgrade beartype As you upgrade, an autonomic thrill of inexpressible joy jolts your limbic system. You rock back into the plush leather chair that is the literal seat of your digital empire with a contented sigh. Feels good to be good. But this episode of "What won't @leycec say for emoji upvotes?" has only begun. Allow Alpha – the artificial general intelligence (AGI) from Hitoshi Ashinano's seminal slice-of-cli-fi-life manga Yokohama Kaidashi Kikou (ヨコハマ買い出し紀行) – to serenade you through
New Challengers Join the Beartype FamilyI'm so maternally proud. Introducing... the newest teenage bear cubs of the @beartype family. They're feisty, because teenage. They're unruly, because bears. They're listening to synthwave-cybermetal mashups that reverberate with pulsating bass lines through your mechanical keyboard keys, because @beartype. They are:
# ML-friendly numeric scalers made simple. That's how we roll.
from beartype import beartype
from numerary import IntegralLike, RealLike
# "int" and "float"? So passé. It's only the best for my Jupyter cell.
@beartype
def deeper_thot(arg: RealLike) -> IntegralLike:
assert arg != 0 and arg ** 0 == 1
return arg // arg + 42
# Plum + numerary: when their powers combine, even /r/Python trembles.
from plum import dispatch
from numerary import RealLike
# Which overload of this function will you call? Why not all three!?!?
@dispatch
def muh_overloaded_func(x: str) -> str:
return "Pretty sure that a string is a string."
@dispatch
def muh_overloaded_func(x: int) -> str:
return "Not sure if positive integer or just happy to see me."
@dispatch
def muh_overloaded_func(x: RealLike) -> str:
return "This is a floating-point... thing. Plum and numerary says so."
pandera: type-check pandas data frames like it's 2027@beartype now transparently supports all pandera type hints. Deeply type-check the contents of almost all pandas objects (including any data frame, index, or series) with type hints published by the third-party pandera package – the leading industry-standard for blah, blah, blah... hey, wait. What is this HR speak in @beartype's automated news dispenser!? Yes. It's true. We are shilling. Seeing is believing. Soon, you too will believe that ML pipelines can be domesticated. Arise, huge example! Stun the disbelievers throwing peanuts at our issue tracker. # Import important machinery. It's important.
import pandas as pd
import pandera as pa
from beartype import beartype
from pandera.dtypes import Int64, String, Timestamp
from pandera.typing import Series
# Arbitrary pandas data frame. If pandas, then data science.
muh_dataframe = pd.DataFrame({
'Hexspeak': (
0xCAFED00D,
0xCAFEBABE,
0x1337BABE,
),
'OdeToTheWestWind': (
'Angels of rain and lightning: there are spread',
'On the blue surface of thine aery surge,',
'Like the bright hair uplifted from the head',
),
'PercyByssheShelley': pd.to_datetime((
'1792-08-04',
'1822-07-08',
'1851-02-01',
)),
})
# Pandera dataclass validating the data frame above. As above, so below.
class MuhDataFrameModel(pa.DataFrameModel):
Hexspeak: Series[Int64]
OdeToTheWestWind: Series[String]
PercyByssheShelley: Series[Timestamp]
# Custom callable you define. Here, we type-check the passed data frame, the
# passed non-pandas object, and the returned column of this data frame.
@beartype
@pa.check_types
def convert_dataframe_column_to_series(
# Annotate pandas data frames with pandera type hints.
dataframe: pa.typing.DataFrame[MuhDataFrameModel],
# Annotate everything else with standard PEP-compliant type hints. \o/
column_name_or_index: str | int,
# Annotate pandas series with pandera type hints, too.
) -> Series[Int64 | String | Timestamp]:
'''
Convert the column of the passed pandas data frame (identified by the
passed column name or index) into a pandas series.
'''
# This is guaranteed to be safe. Since type-checks passed, this does too.
return (
dataframe.loc[:,column_name_or_index]
if isinstance(column_name_or_index, str) else
dataframe.iloc[:,column_name_or_index]
)
# Prints joyful success as a single tear falls down your beard stubble:
# [Series from data frame column by *NUMBER*]
# 0 3405697037
# 1 3405691582
# 2 322419390
# Name: Hexspeak, dtype: int64
#
# [Series from data frame column by *NAME*]
# 0 Angels of rain and lightning: there are spread
# 1 On the blue surface of thine aery surge,
# 2 Like the bright hair uplifted from the head
# Name: OdeToTheWestWind, dtype: object
print('[Series from data frame column by *NUMBER*]')
print(convert_dataframe_column_to_series(
dataframe=muh_dataframe, column_name_or_index=0))
print()
print('[Series from data frame column by *NAME*]')
print(convert_dataframe_column_to_series(
dataframe=muh_dataframe, column_name_or_index='OdeToTheWestWind'))
# All of the following raise type-checking violations. Feels bad, man.
convert_dataframe_column_to_series(
dataframe=muh_dataframe, column_name_or_index=['y u done me dirty']))
convert_dataframe_column_to_series(
dataframe=DataFrame(), column_name_or_index=0)) See our extensive FAQ entry on the topic for further discussion. There be dragons belching flames over the hapless village. You want to know about those dragons. The hapless village is actually your codebase in this awkward metaphor.
PEP 591:
|
*Glorp*, *glorp*, *glorp*. This is the sound that disgusting alien pupae in the 80's scifi horror film you just made your wife watch makes when it's giving birth to... python3 -m pip install --upgrade beartype We gestated this rapid release under our new "Fast is better than slow" motto. Will @beartype be able to keep up the frantic pace? Does @leycec need sleep? Is this a healthy lifestyle for anyone? The answer to these questions and more is: "Almost certainly not." We're gonna have to read Japanese light novels for the next five months to recover. Thankfully, we did something first. Get your PEP 673 (i.e.,
The Pillars of Creation? Or Creepy Cosmic God-Hand of Retribution? You decide. PEP 673: Taking QA Selfies with
|
@beartype 0.15.0 arises from the ashes of our issue tracker. Now, so too will your codebase. python3 -m pip install --upgrade beartype Like a cyberpunk phoenix whose intertube veins are made of pure honey and blueberry juice, @beartype 0.15.0 introduces the new When you call import hooks published by the That's right. @beartype is now a tentacular cyberpunk horror like that mutant brain baby from Katsuhiro Otomo's dystopian 80's masterpiece "Akira." You can't look away!
Does this mean that you can now safely discard mypy,
In either case, tl;dr: Just Tell My Coworker What to Do, Daddy @leycecFor those who are about to code, we salute you with all you need to know: # In your "{your_package}.__init__" submodule:
from beartype.claw import beartype_this_package # <-- boilerplate for victory
beartype_this_package() # <-- congrats. your team just won. That's it. That's
Okay, that's not it. The
Will The Real Import Hooks Stand Up?
|
👏👏👏👏👏👏 well met, @leycec! I'm excited to give this a try soon! |
This is awesome! Marvelous work @leycec. And because I love |
Whoa. Absolutely fantastic work, @leycec! This is insanely cool. |
By the way, this issue is still pinned; idk what you're going to do next, but it might be time to just let this 1GB webpage sleep, and pin the blame on something else. |
Wahoo! Thanks so much for the phenomenal outpouring of support. Because I Let us abruptly change the subject.
@patrick-kidger: Yes! So much yes! Miraculously, Before implementing Is this a real-world problem? This is a real-world problem. I'm shocked ...simply shocked, I say that Python never standardized an import hook architecture. It didn't. The result is chaos and anarchy on the mean streets of PyPI. I quietly abandoned the standard approach. Instead, I reverse-engineered Python's This means there are guaranteed to be no conflicts between And... maybe I should have redacted the last two self-congratulatory paragraphs. |
Haha, no worries! So, jaxtyping and pytest's import hooks do both work together. That's something that "just worked" without intervention from me. I don't think there's an fundamental incompatibility there. I've not tested but I assume they're both just rewriting the AST one after the other, e.g. much like a So I'm curious what went wrong when you tried using the standard approach with beartype? (Side note, I'm curious if you have a link to the new version you've cooked up?) |
Greetings from a distant past, fellow Pythonista. By the time you read this, everything below has already become a nostalgic time capsule obsoleted by the sandy hands of Time and Guido. Still, let's do it.
In this pinned issue, we discuss where
beartype
is going, wherebeartype
has been, and how we can get from here to 1.0.0 without hitting any more code pigeons with whom we had a deal.But first...
Let's Talk About You!
...because you are awesome. Here are all the ways we hope to build a vibrant
O(1)
community that will outlive even cyborg head-in-a-jar @leycec.Forum
Our
forum"GitHub Discussions" is now up. Thanks to the explosive growth in both GitHub stars and page views, GitHub automatically unlocked its beta forum implementation for us. Hooray!Ask me anything (AMA). I promise to answer at least 10% of all questions –
with particular attention to weeb genre tags like Japan, video games, heavy metal, scifi, fantasy, and the intersection of those five hot topics. So Japanese scifi-fantasy video games with metal OSTs. They exist, people.
Wiki
Our wiki is now open to public modifications. Since
beartype
is trivial to install (pip
), configure (no configuration), and use (no API), there's currently no incentive for a wiki. I acknowledge this. My glorious dream is that the wiki will be an extemporaneous idea factory and whiteboard for unsound and probably dangerous methods of constant-time runtime type checking.If that fails to manifest and the wiki just devolves into a spam-clogged cesspit of black-hat depravity, we'll reassess. Until then, do what thou wilt shall be the whole of the Law.
Pull Requests
We greedily welcome any pull request no matter how small or thanklessly ambitious. However, note that volunteer contributions will be... complicated. On the one hand,
beartype
is meticulously documented, commented, and tested. On the other hand,beartype
is internally implemented as a two-pass type checker:O(1)
type checker that only tests whether all passed parameters and returned values satisfy all type hints.O(n)
type checker that raises human-readable exceptions in the event that one or more passed parameters or returned values violate a type hint.Also, did we mention that the first pass is stupidly overtuned with cray-cray memoization, micro-optimizations, and caching? Despite my best efforts, this means that meaningful pull requests may never happen. I admit that this is non-ideal – but also unavoidable. Speed and PEP-compliance (in that order) are our primary motivations here. Maintainability and discoverability are tertiary concerns.
This is the high cost of adrenaline. So it goes.
The Future: It Looks Better Than the Past
Sloppy Markdown calendars >>>> real project management that middle-management always stuffs into Excel macro-driven Gantt charts, so:
beartype
Play video games until vomiting across a bucket.More video games. Less vomiting. It is a reasonable hope.beartype
0.6.0, Part Un. (That means "one" in Quebec.)beartype
0.6.0, Part Deux. Ship it.beartype
0.7.0, Part Un.beartype
0.7.0, Part Deux. Ship it.beartype
0.8.0, Part Un.beartype
0.8.0, Part Deux. Ship it.beartype
0.9.0, Part Un.beartype
0.9.0, Part Deux. Ship it.beartype
1.0.0, Part Un.beartype
1.0.0, Part Deux. But we are not ready yet!beartype
1.0.0, Part Trois. Ship it.The official roadmap says one year to 1.0.0. Will we make it? Defly not. We'll fumble the pass in the final yard, stumble into the enemy mascot, and land in the celebratory Gatoraid™ bucket as the ref slashes the air with a red card to mass hysteria from the crowbar-wielding rowdy crowd.
The official roadmap cannot be denied, however.
Beartype 0.6.0: The Mappings Are Not the Territory
Beartype 0.6.0 intends to extend deep type-checking support to core data structures and abstract base classes (ABCs) implemented via the
hash()
builtin, including:dict
.frozenset
.collections.ChainMap
.collections.OrderedDict
.collections.abc.DefaultDict
.collections.abc.ItemsView
.collections.abc.KeysView
.collections.abc.Mapping
.collections.abc.MutableMapping
.collections.abc.MutableSet
.collections.abc.Set
.collections.abc.ValuesView
.typing.ChainMap
.typing.DefaultDict
.typing.Dict
.typing.FrozenSet
.typing.ItemsView
.typing.KeysView
.typing.Mapping
.typing.MutableMapping
.typing.MutableSet
.typing.OrderedDict
.typing.Set
.typing.ValuesView
.beartype
currently only shallowly type-checks these type hints. We can do better. We must do better! The future itself may very well depend upon it.These are among the last big-ticket hints we need to deeply type-check, but they're also the least trivial. Although the C-based CPython implementation almost certainly stores both set members and dictionary keys and values as hash bucket sequences, it fails to expose those sequences to the Python layer. This means
beartype
has no efficient random access to arbitrary set members or dictionary keys and values.Does that complicate
O(1)
runtime type-checking of sets and dictionaries? Yes. Yes, it does. I do have a number of risky ideas here, most of which revolve around internal caches ofKeysView
,ValuesView
, andItemsView
iterators (i.e., the memory views returned by thedict.keys()
,dict.values()
, anddict.items()
methods). I don't want to blow anything up, so this requires care, forethought, and a rusty blood-flecked scalpel.Memory views only provide efficient access to the next dictionary object iterated by those views. This means the only efficient means of deeply type-checking one unique dictionary object per call to a
@beartype
-decorated callable is forbeartype
to internally cache and reuse memory views across calls. This must be done with maximum safety. To avoid memory leaks, cached memory views must be cached as weak rather than strong references. To avoid exhausting memory, cached memory views must be cached in a bounded rather than unbounded data structure.The stdlib
@functools.lru_cache
decorator is the tortoise of Python's caching world. Everyone thinks it's fast until they inspect its implementation. Then they just define their own caching mechanism. Of course,beartype
did exactly that with respect to an unbounded cache: our privatebeartype._util.cache.utilcachecall
submodule defines the fastest-known pure-Python unbounded cache decorator, which we liberally call everywhere to memoize internalbeartype
callables.Now, we'll need to define a similar bounded cache that caches no more than the passed maximum number of cache entries. This isn't hard, but it's something that needs to be done that takes resources. This is why my face looks constipated on a daily basis.
But that's not all. We then need to populate that cache with weak references to memory views dynamically created and cached at call time for
@beartype
-decorated callables inO(1)
time with negligible constants. In Pythonic pseudocode, this might resemble for the specific case of item views:@beartype
would then generate wrappers internally calling the aboveget_dict_nonempty_next_item()
function to obtain a basically arbitrary (...yes, yes, insertion order, I know, but let's just pretend because it's late and I'm tired) mapping key, value, or key-value pair to be deeply type-checked inO(1)
time. This is more expensive than randomly deeply type-checking sequence items, but hopefully not prohibitively so. It's all relative here. As long as we can shave this down to milliseconds of overhead per call, we're still golden babies.Note that we basically can't do this under Python < 3.8, due to the lack of assignment expressions there. Since
get_dict_nonempty_next_item()
returns a new key-value pair each call, we can't repeatedly call that for each child pith and expect the same key-value pair to be returned. So, assignment expressions under Python >= 3.8 only.</shrug>
Under Python < 3.8,
beartype
will fallback to just unconditionally deeply type-checking the first key, value, item, or member of each passed or returned mapping and set. That's non-ideal, but Python < 3.8 is the past and the past is bad, so nobody cares. It's best that way.Beartype 0.7.0: Well-hung Low-hanging Fruit
Beartype 0.7.0 intends to extend deep type-checking support to non-core data structures and abstract base classes (ABCs) – each of which is trivial to support in isolation but all of which together will break me like a shoddy arrow over their knees:
type
.collections.deque
.collections.Counter
.collections.abc.Collection
.collections.abc.Container
.collections.abc.Iterable
.collections.abc.Reversible
.typing.Collection
.typing.Container
.typing.Counter
.typing.Deque
.typing.Iterable
.typing.NamedTuple
.typing.Reversible
.typing.Type
.typing.TypedDict
.It is doable. Will it be done? Four out of five respondents merely shrug.
Beartype 0.8.0: Calling All Callables
Beartype 0.8.0 intends to extend deep type-checking support to callables:
collections.abc.AsyncIterable
.collections.abc.Awaitable
.collections.abc.Callable
.collections.abc.Coroutine
.typing.AsyncIterable
.typing.Awaitable
.typing.Callable
.typing.Coroutine
.Dynamically type-checking callables at runtime in
O(1)
is highly non-trivial and (maybe) even infeasible.Some types of callables can't reasonably be deeply type-checked at all at runtime. This includes one-time-only constructs like generators and iterators, which can't be iterated without being destroyed. So, the Heisenberg Uncertainty Principle of Python objects.
Most types of callables can reasonably be deeply type-checked at runtime, but it's unclear how that can happen in
O(1)
time. The only non-harmful approach is to ignore the callables themselves and mono-focus instead on the annotations on callables. Specifically:@beartype
-decorated callables. Since the arguments subscripting a callable type hint are finite and typically quite small in number (e.g.,collections.abc.Callable[[int, str], dict[float, bool]]
), there is little incentive to randomize here. Instead, we generate code resembling the code we currently generate for fixed-length tuples (e.g.,tuple[int, str, float]
).This will necessitate looking up annotations for stdlib callables in the infamous third-party
typeshed
, which complicates matters. Whose bright idea was it, anyway, to offload annotations for stdlib callables onto some third-party repo? That's what PEP 563 is for, official Python developers. PEP 563 means nobody has to care about the space or time costs associated with annotations anymore. Maybe we should actually use that.</sigh>
Beartype 0.9.0: Type Variables Mean Pain
Beartype 0.9.0 intends to extend deep type-checking support to parametrized type hints (i.e., type hints subscripted by one or more type variables). I won't even begin to speculate how this will happen. I have a spontaneous aneurysm every time I try thinking about going deeper than a toy example like
def uhoh(it_hurts: T) -> list[T]
. It's when you get into type variables subscripting arbitrarily nested container and union type hints that my already meagre mental faculties begin unravelling under the heavy cognitive load.It is possible to do this. But doing this could mean the last threads of my already badly frayed sanity. Challenge accepted.
Beartype 1.0.0: All the Remaining Things
Beartype 1.0.0 intends to extend deep type-checking support to everything that's left – some of which may not even reasonably be doable at runtime. The most essential of these include:
@beartype
decorator to support both callables and classes is both critical and trivial – with one notable exception: parametrized classes (i.e., user-defined generic classes subclassing eithertyping.Generic
ortyping.Protocol
, subscripted by one or more type variables). Supporting parametrized classes will probably prove to be non-trivial, because it means constraining types not merely within each class method but across all class methods annotated by parametrized type hints, which means that state needs to be internally preserved between method calls, which we currently do not do at all, because that is unsafe and hard. But that's fine. That's what we're here for. We're not here for Easy Mode™ type checking. We're here because this is the Soulsborne of the type-checking world. If it isn't brutal, ugly, and constantly stealing your soul(s), it ain'tbeartype
.typing.Literal
, thetyping
hint formerly known as PEP 586. Whiletyping.Literal
itself is mostly inconsequential, supporting that hint inbeartype
requires a significant refactoring that should yield speedups across the board for all other hints. Well, isn't that special?Let's trip over the above issue-laden minefields when we actually survive the preceding milestones with intact sanity, keyboard, and fingernails. Phew.
Beyond the Future: What Does That Even Mean?
Beartype 1.0.0 brings us perfect compliance constant-time compliance with all annotation standards. That's good. But is that it?
It is not.
Beartype + Sphinx + Read the Docs = A Match Made in Docstrings
The
beartype
API should be up on Read the Docs. It isn't, because we are slothful and full of laziness. Righting this wrong is a two-step process:#. Enable local generation of Sphinx-based HTML documentation. Fortunately, we've judiciously documented every class, callable, and global throughout the codebase with properly-formatted reStructuredText (reST) in preparation for this wondrous day. The only real work here will be adding a top-level
docs/
directory containing the requisite Sphinx directory structure. Still, it's probably one to two weeks worth of hard-headed volunteerism.#. Enable remote hosting of that documentation on Read the Docs. I've never actually done this part before, so this will be the learning exercise. We'll probably need to wire up our GitHub Actions-based release automation to generate and publish new documentation with each stable release of
beartype
.tl;dr: critical and trivial, if a little time-consuming. Let's do docs! And this leads us directly to...
Beartype Configuration API: It's Happening
beartype
currently has no public API... effectively. Technically, of course:beartype.cave
submodule is a public clearing house for common types and tuples of types, which was essential in the early days before we implemented full-blown PEP 484 compliance. Now? Vestigial, bro.beartype.roar
submodule exposes public exceptions raised by the@beartype
decorator at decoration time and by wrapper functions generated by that decorator at call time. Since there's probably no valid use case for actually catching and handling any of these exceptions in external third-party code, this submodule doesn't do terribly much for anyone either.Neither of those submodules could be considered to be an actual API. Post
beartype 1.0.0
, we'd like to change that. The most meaningful change is the change everyone (including me, actually!) really wants: the flexibility to configurebeartype
to deeply type-check more than merely one container item per nesting level per call. WhileO(1)
constant-time type-checking will always be thebeartype
default, we'd also like to enable callers to both locally and globally enable:O(log n)
logarithmic-time type-checking. Type-checking only a logarithmic number of container items per nesting level per call strikes a comfortable balance between type-checking just one and type-checking all containers items. Of course, this will still necessitate randomly selecting container items. Rather than just generating one random index each call, however, callables decorated withO(log n)
type-checking will need to generate a logarithmic number of random indices each call. Since the Python standard library provides no means of doing so, we need to look further afield. The most efficient means of doing so that is commonly available is thenumpy.random.default_rng().integers()
method. On systems lackingnumpy
,beartype
will probably ignore attempts to enableO(log n)
type-checking by emitting a non-fatal warning and falling back toO(1)
type-checking. Other than that, this seems mostly trivial to implement. Good 'nuff.O(n)
linear-time type-checking, affectionately referred to as "full fat" type-checking by an early adopter in a thread I've long since misplaced. Linear-time type-checking is reasonable only under certain ideal conditions that most callables fail to satisfy – like, say, callables guaranteed to receive and return either containers no larger than a certain size or larger containers that are only ever received or returned (and thus type-checked) exactly once throughout the entire codebase. But sometimes you absolutely know those things are the case (...at least for now) and you're willing to play dice with the DevGodsOfCrunch that that will absolutely, probably, hopefully never change. This is even more trivial to implement. But don't blame us when your entire web app crunches to a halt and you get "the call" on a Saturday night. We told you so.n
:n <= 10
, performO(n)
type-checking on those containers.n <= 100
, performO(log n)
type-checking on those containers.O(1)
type-checking on those containers.Here's how this API might shape out in practice. First, we define a new
beartype.config
submodule declaring a new enumerationContainerStrategy = Enum('ContainerStrategy', 'O1 Ologn On Hybrid')
enabling callers to differentiate between these strategies. Then, we augment the@beartype
decorator to accept an optionalcontainer_strategy
parameter whose value is a member of this enumeration that (wait for it) defaults toO1
.Here's how that API would be used in practice:
Let's say you decide you like that. Thanks to the magic of the
functools.partial()
function, you could then mandate that strategy throughout your codebase as follows:You would then decorate callables with your custom
@beartype_Ologn
decorator rather than the default@beartype
decorator. This conveniently circumvents the need forbeartype
to define a global configuration API, which I'm reluctant to do, because I'm lazy. Full stop.Of course, we should probably just publicly define
@beartype_Ologn
and@beartype_On
decorators for everybody so that nobody even has to explicitly bother withfunctools.partial()
. We should. But... we're lazy!And now for something completely different.
Beyond Typing PEPs, There Lies Third-Party Typing
Third-party types include all of the scientific ones that made Python a household name in machine learning, data science, material science, and biotechnology over the past decade – including:
There will probably never be a standard for type-hinting these types, because these types reside outside the Python standard library.
But that doesn't mean we're done here. We can produce type hints that are both PEP-compliant and generically usable at runtime by any runtime type checker (as well as by static type checkers with explicit support for those hints).
How? By leveraging PEP 3119 - "Introducing Abstract Base Classes", which standardized the
__isinstancecheck__()
and__issubclasscheck__()
metaclass dunder methods. These methods enable third-party developers to dynamically create new classes on-the-fly that:The idea here is to extract that machinery into a new PyPI-hosted package named
deeptyping
(or something something) that declares one type hint factory for each of the above data structures.deeptyping
will be designed from the ground-up to be implicitly useful at both static time and runtime by performing deep type-checking on runtime calls to theisinstance()
andissubclass()
builtins. This stands in stark contrast to the standardtyping
module, which almost always prohibits calls to those builtins and is thus mostly useless at runtime. Ergo, "deep" typing.For example, here's what PEP-compliant NumPy array type hints might look like:
The
deeptyping.NumpyArray()
class factory function would have signature resembling:That function would be implemented as a class factory dynamically creating and returning new memoized classes compliant with PEPs 3119 and 484. For example, here's untested pseudocode (that will blow up horribly, which will make me feel bad, so don't try this) for the metaclass of the class that function might create when passed the above parameters:
There are probably substantially better ways to do that. Hopefully, it would suffice to statically declare merely one generic metaclass that would then be shared between all dynamically created classes with that metaclass returned by the
deeptyping.NumpyType()
factory function.Anyways. Everything above is pure speculation. The overarching point is that neither
beartype
nor any other runtime type checker will need to be refactored to depend on or otherwise explicitly supportdeeptyping
, because all PEP 484-compliant runtime type checkers already implicitly support that sort of thing. It is good.Technically, there do exist third-party packages violating all annotation standards that support typing hinting of a few of the above data structures. The third-party
Traits
package, for example, supports NumPy-specific type hints – but only by violating all annotation standards. That rather defeats the point of standards. From the important perspective of PEP-compliance, it is bad.Thus
deeptyping
.Beyond
typing_inspect
, There Liespepitup
The dirty little secret behind Python's annotation standards is that they all lack usable APIs. No, the
typing.get_type_hints()
function doesn't count, because that function doesn't do anything useful that you can't do yourself while doing a whole lot that's non-useful (like being face-stabbingly slow). The only exception is PEP 544 -- "Protocols: Structural subtyping (static duck typing)", which does surprisingly have a usable API. That's nice.This pains us. In theory, it shouldn't be possible to get any Python Enhancement Proposal (PEP) that lacks a usable API passed the peer review process – let alone the seven PEPs lacking usable APIs that
beartype
currently supports.But here we are. It happened. It happened because the official Python developer community wrongly perceived static type checking to be the only useful kind of type checking. Most annotation PEPs never even use the adjective "runtime" except in an incidental or derogatory manner. PEP 484, for example, declares:
No, its significance is to all type checkers. Who wrote that?
Of course, it's never explained why static type-checking is the most (by which they mean "only") important goal. It's just assumed a priori that runtime type-checking is sufficiently insane, inefficient, and ineffectual as to be universally unimportant – when, in fact, static type-checking of dynamically-typed languages is insane by definition.
Runtime type checkers can decide entire classes of decision problems undecidable by static type checkers, most of which are industry standard throughout the Python community.
Runtime type checkers also never report false negatives (not even for one-shot objects unintrospectable at runtime like generators and iterators). Runtime type checkers never have to guess, infer, or otherwise derive types. But static type checkers always do those things, because guesswork is all they do.
So
beartype
challenges common assumptions merely by existing. But that's not enough. We may have solved runtime type-checking by internally implementing private stand-in APIs for all these PEPs, but nobody else can safely access that or reuse our efforts.We don't have our Delorean yet, so we can't retroactively go back and fix this in the past. Even adding public APIs for these PEPs to the Python standard library wouldn't really help anyone, because Python 3.9 will still be alive through most of 2025. But that doesn't mean we just have to "suck it up."
The third-party
typing_inspect
package partially solves this problem by defining a limited public API for these PEPs. But it only supports:Because of these limitations, you couldn't reimplement
beartype
based ontyping_inspect
, for example. But we can publicizebeartype
internals as our own separate third-party package that supports:beartype
currently sequesters these internals to the private nestedbeartype._util.hint.pep
subpackage, with associated machinery haphazardly strewn about. The idea here is to extract that machinery into a new PyPI-hosted package namedpepitup
(or something something) and then refactorbeartype
to depend on that package.It's a nice idea. But nice ideas often never happen. Let's see if this one does!
Influencer versus Introvert: Introvert Wins!
And last but certainly not least... influencing. Let's recount the ways @leycec should start banging on that social influencing drum to hype up the nascent
O(1)
runtime type-checking scene:beartype
is the table flip of the type-checking world. Cue that emoji.(╯°□°)╯︵ ┻━┻
beartype
behaves fundamentally differently (both theoretically and practically) from all existing type checkers – runtime or not. This means we have more than a few novel things to talk about. Clearly, I like talking. Let's be honest, right? So that's not the issue. The issue is that passing peer review as an independent scientist with no current University affiliation isballs hardnon-trivial. But I'm committed to doing this. Without publication, no one can formally citebeartype
in their own publications, which meansbeartype
is invisible to academia, which is bad. Moreover, publication constitutes a soft (maybe hard) prerequisite for securing eventual grant funding from governmental agencies in Canada and the U.S., the two nations I hold dual-citizenship in. For sanity, publication will probably happen in incremental stages:beartype
features, tradeoffs, and complications. I'm fluent in LyX (which I love) and conversant in LaTeX (which I loathe), so this should be "fun" for several qualifying definitions of "fun."beartype
aficionados, motivators, and early adopters with University affiliation for their totally nice and life-affirming constructive criticism. After my fragile ego recovers from the death blows by e-mail, I'll scrap the whole report and rewrite it from the ground up for...siezure-inducinghypnotic flashing text littering every ad-strewn header.O(1)
to go the whole nine yards. Let's see if @leycec can extricate himself from the seductive yet limiting INTP shell long enough to actualize this.Phew. That real-world stuff really isn't as easy as it looks.
And... We're Done
🤯
The text was updated successfully, but these errors were encountered: