-
-
Notifications
You must be signed in to change notification settings - Fork 50
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
[Feature Request] Postponed evaluation of non-self-referential type hints #226
Comments
Thanks so much for both your generous interest and this perfect issue report, @eohomegrownapps. Smiley cat is most pleased. 😸 Does @beartype Suck As Much As I Think?Sadly, you are indeed correct; forward references are our Achilles heel. It's trivial for a static type-checker like mypy to resolve forward references, because static type-checkers don't actually run anything; they just parse. It's comparably harder for a runtime type-checker like
How Can I Just Make This Work Now?Thankfully, from beartype import beartype
from beartype.typing import List
from beartype.vale import Is
from dataclasses import dataclass
@beartype
def my_fn(xs: List['MyCls']):
return xs[0]
@dataclass
class MyCls:
value: int
babes_everywhere = MyCls(value=0xCAFEBABE)
assert my_fn([babes_everywhere]) is babes_everywhere Mypy and @beartype alike should now both be happy. It's a party and you're invited. But My Second Example Is Still Busted, Right?...yeah. Sadly, the same approach fails to extend to your second example. Happily, that example can be rewritten to leverage the standard from dataclasses import dataclass
from beartype import beartype
from beartype.typing import TYPE_CHECKING, Annotated, Generic, TypeVar
T = TypeVar("T")
# If a static type-checker like mypy is currently running, prefer a
# standard forward reference.
if TYPE_CHECKING:
MyClsInt = 'MyCls[int]'
# Else, a runtime type-checker like @beartype is currently running.
# In this case, fallback to a simple reference to "MyCls". Since
# user-defined subscripted generics (e.g., the "[int]" in "MyCls[int]")
# convey no meaningful semantics at runtime, @beartype would just
# silently ignore them anyway. Help a @beartype out, bro!
else:
MyClsInt = 'MyCls'
@beartype
def my_fn(x: MyClsInt):
return x.value
@dataclass
class MyCls(Generic[T]):
value: T
assert my_fn(MyCls(value=0xCAFEBABE)) == 0xCAFEBABE In this case, we leverage
How Do We Help @beartype Suck Less?What follows is mostly notes to myself. I will forget this. Now, I won't. Checkmate, ageing brain. 🧠 To resolve this,
I am shuddering already. When Will You Do This For Everybody?Why, I'm so glad you asked. We'll probably nail this to floor during our upcoming |
Instead of writing the parser manually, there's a simpler approach, somewhat described here for PEP 649 (Deferred Evaluation Of Annotations Using Descriptors). Instead of executing the annotation normally, instead a custom dict subclass could be passed as the locals dictionary. That allows intercepting all namespace lookups. So we could do a normal lookup of globals/locals, but if a lookup fails, create and inject a proxy object which has all the annotation-relevant special methods defined. Or even unconditionally return a proxy for every name, perhaps to construct a tree of |
@leycec ... wow. that probably wins the award for most characterful (and fastest) github issue response I've ever encountered 😸 Unfortunately, I'm not sure how scalable this temporary fix will be, considering the codebase I'm developing is a type-based abomination
This is a slightly off-topic thing, but it would be really awesome to have beartype inspect user-defined subscripted generics to determine (in most cases, with high probability) whether they typecheck... I imagine, though, this would require a little too much python hackery / performance cost of tracking typevars to be palatable (?)
(Also, this was referring to issue #43, not this feature :) ) |
Yup. You're too kind, @eohomegrownapps. Let's admit that everything I wrote is totally anti-scalable, because it puts the burden of type-checking forward references almost entirely on you: the hard-working developer. That's awful! I acknowledge this is badness incarnate. @beartype should address this as soon as humanly feasible. I promise the Summer will bring blue skies, empty beaches, and a @beartype that actually works. 🌞
Yes! I'd love to have that, too. Sadly...
...heh. That's the concern, really. To do this sanely,
Clearly, that's tractable. Well, probably. Given sufficient volunteer man-hours, @beartype can definitely do that – probably. Yeah! Clearly, however, that's also non-trivial. This will definitely happen – eventually. But "eventually" might be a very long wait indeed. Runtime type-checking of generic type variables isn't even something that anyone thought was possible before. PEP 484 literally instructs runtime type-checkers to just ignore type variables. Scaredy emoji cat is distraught. 🙀 |
@TeamSpen210: OMG. You're a friggin' genius, Spence! Not only that, but... you're friggin' alive. Miraculously, you made it through another gruelling winter. I thought you were a goner for sure. It's been a literal lifetime since we've heard from you here. Spring, sunshine, and good fortune are surely smiling upon @beartype today. Okay. Back to brass tacks. This...
...is all stupendous. You're absolutely right about everything, like usual. That's the aikido throw I've been searching for. Writing some ad-hoc hand-rolled slow-as-molasses fragile-as-bone-china parser would be terrible. You have saved me from a terrifying fate of darkness. As additional notes to myself:
from beartype._decor._cache.cachetype import bear_typistry
class _BeartypeForwardRefMeta(type):
def __instancecheck__(cls: BeartypeForwardRef, obj: object) -> bool:
'''
*BOOM.*
'''
cls_resolved: type = cls.resolve()
return isinstance(obj, cls_resolved)
class BeartypeForwardRef(object, metaclass=_BeartypeForwardRefMeta):
def __init__(self, forwardref: str) -> None:
#FIXME: Do some preliminary validation of this string for sanity.
#Notably, validate that this string is a valid Python identifier.
self.forwardref = forwardref
def resolve(self) -> type:
'''
Resolve this forward reference to the type it refers to.
'''
# Good enough for now. Our existing "bear_typistry"
# dictionary already handles lookup-based resolution and
# caching of forward references at runtime; so, just defer
# to that for now as the trivial solution.
#
# Next!
return bear_typistry[self.forwardref]
def __class_getitem__(cls, *args: object): -> None:
'''
Record the passed child type hints subscripting this
forward reference to a user-defined generic (e.g.,
the :class:`int` in ``MuhGeneric[int]``).
'''
return _BeartypeForwardRefGenericSubscripted(
forwardref=forwardref, args=args)
class _BeartypeForwardRefGenericSubscripted(BeartypeForwardRef):
'''
Forward reference to a user-defined generic subscripted
by one or more child type hints (e.g., ``MuhGeneric[int]``).
'''
def __init__(self, forwardref: str, args: tuple) -> None:
super().__init__(forwardref)
#FIXME: Actually do something with subscripted child type hints.
#For now, simply ignoring them outright is the sane thing to do.
self.args = args This is actually incredible. Thanks to metaclass >>> isinstance(MyCls(), BeartypeForwardRef('MyCls'))
True
>>> isinstance(MyCls[int](), BeartypeForwardRef('MyCls')[int])
True This should substantially streamline @beartype's code generation. Minds are being blown. 💥 🤯 |
I'm encountering a similar issue, but in a different setup, where the exception is @dataclass
class AbstractKernel(Module):
...
active_dims: Optional[List[int]] = static_field(None) and a subclass in another file @dataclass
class RFF(AbstractKernel):
... which doesn't redefine Would it be helpful for you to have a MRE of this, too? (Asking first because I'm using beartype with jaxtyping and |
Gah! Tremendous apologies for the woes that @beartype has brought, @st--. And might I add... what a username that is. I didn't even know you could dangerously suffix usernames by arbitrarily many hyphens. Your valiant attempts to break GitHub impress me. I wonder why @beartype is failing you in this seemingly reasonable from beartype import beartype
from beartype.typing import Optional, List
from dataclasses import dataclass
@beartype
@dataclass
class AbstractKernel(object):
active_dims: Optional[List[int]] = None
@beartype
@dataclass
class RFF(AbstractKernel):
pass I beg of you, @st--. Please shower us with a brilliant MLE that will stun me even further! Actually, would you mind just submitting a new issue? I suspect this is probably a unique issue specific to some combination of JAX, There's a lot of third-party intensity going on here. I didn't even know that I'm currently banging on a native import hook for @beartype. It's intended to be the Ultimate-Super-Saiyan import hook, because it augments @beartype into the first hybrid static-runtime type-checker for any dynamic language. Think mypy, except it runs at runtime and lies a bit less. Please find enclosed an accurate visual depiction of @beartype's upcoming import hook.
|
It's the File from jaxtyping import install_import_hook
with install_import_hook("mre", "beartype.beartype"):
from .base import *
from .rff import * File from __future__ import annotations
from beartype.typing import List, Optional
from dataclasses import dataclass
@dataclass
class AbstractKernel:
"""Base kernel class."""
active_dims: Optional[List[int]] = None File #from __future__ import annotations
from dataclasses import dataclass
from .base import AbstractKernel
@dataclass
class RFF(AbstractKernel):
pass Then
If you comment out the I previously got this error:
when both modules had the ( |
Ah-ha! So. The horrifying truth reveals itself at last. Thanks so much for that awesome MRE, @st--. Since I kinda need to push out a new # This is painfully dumb, but sometimes miracles happen.
from __future__ import annotations # <-- *THIS. TRY THIS.*
from jaxtyping import install_import_hook
with install_import_hook("mre", "beartype.beartype"):
from .base import *
from .rff import * That's it. Just prepend It saddens me that both GitHub and Google are united in their shared hatred for your username. It's fascinating that Google would rather reject volunteer contributions1 than fix their clearly busted CLA validator. Like, how hard is it to just accept legacy usernames? It's not. We know. There's a poorly documented and thoroughly untested regex somewhere deep within the bowels of GitHub and Google. It alone is to blame for all the bad things in this world. 1. Volunteer contributions are the highest value contributions that you can get. They're even more valuable than paid employee contributions – because you had to pay employees to make them, which means they didn't really care in the first place. Code is where the heart is. I salute your busted username, @st--. Even if nobody else cares, @beartype cares. 🐻 |
I've tried it out but I'm afraid adding the future annotations import to |
Relatedly, @Asana typing giant @skeggse submitted this excellent minimal-reproducible example (MRE) exhibiting the same issue. Clearly, this is more mission-critical than I wanted to believe: import abc
from typing import Type, TypeVar
from beartype import beartype
A = TypeVar("A", bound="Mixin")
class Mixin(abc.ABC):
@classmethod
def schema(cls: Type[A]) -> "SchemaType[A]":
pass
def mix(cls):
cls.schema = classmethod(Mixin.schema.__func__)
Mixin.register(cls)
return cls
@beartype
@mix
class Custom:
pass ...which raises the expected bad exception: beartype.roar.BeartypeDecorHintForwardRefException: Forward reference
"__main__.SchemaType[A]" syntactically invalid as module attribute name. I've been delaying addressing this due to #226, which will either redefine the face of Python as we know it or catastrophically blow up in my face. Both of those things might even happen. |
This commit is the first in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit: * Defines a new private `beartype._decor.cache.cachescope` submodule. * Defines a new `BeartypeableScopeDeferrer` dictionary subclass in that submodule. Naturally, this subclass is only partially defined, mostly undocumented, and thoroughly untested. The journey of a thousand commits begins with a single broken commit. (*A little belittling is bewitching, but which?*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit: * Defines a new private `beartype._util.cls.utilclsmake` submodule. * Defines a new `make_type()` utility function dynamically creating and returning new classes subclassing arbitrary superclasses containing arbitrary class attributes. Surprisingly, this function has been exhaustively tested and documented. Praise be to summer in Canada. (*Untold cold calderas under an endurable morass!*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit: * Generalizes our existing private `@beartype._util.cache.utilcachecall.callable_cached` decorator to memoize variadic positional arguments. Previously, that decorator only memoized non-variadic positional arguments. * Defines a new private `beartype._util.cache.utilcachemeta.BeartypeCachingMeta` metaclass caching immutable instances of classes whose metaclasses are this metaclass, cached via the positional arguments instantiating those classes. * Exhaustively unit tests these improvements. (*Wondrous wand or suss couscous?*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit: * Defines a new private `beartype._decor.forward.fwdref` submodule. * Defines a variety of functionality pertaining to forward references in this submodule -- most of which is private to that submodule and exhibits black and magical capacities for hitherto unknown darknesses (e.g., a PEP 3119-compliant metaclass defining the dark `__instancecheck__()` dunder metaclass method). Indeed, the only public attribute is the `make_forwardref_subtype()` factory function, which dynamically creates and returns new **forward reference subclasses** (i.e., classes whose metaclass defines the `__instancecheck__()` method to dynamically resolve the forward references encapsulated by those classes). Nothing is tested. Everything is broken. This is the way we code: with maximum adrenaline and insufficient safety precautions. (*Liminal limes in a collinear colander!*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit: * Begins drafting an initial implementation of the pivotal `beartype._decor.forward.fwdscope.BeartypeForwardScope.__missing__()` method. * Shifts the prior `beartype._decor.cache.cachetype` submodule to `beartype._decor.forward.fwdtype` for orthogonality. (*Indecisive incisor or incision in a decision?*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit: * Generalizes our recently added `beartype._decor.forward._fwdref` submodule to support arbitrary `isinstance()` resolution -- including *eventual* resolution of recursive type hints (which necessarily employ forward references to convey the recursion). * Shifts the prior `beartype._decor.cache.cachedecor` submodule to `beartype._decor.decorcache` for orthogonality. * Removes the now-empty `beartype._decor.cache` subpackage. (*Not a peep from deep peeps!*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit: * Generalizes our recently added `beartype._decor.forward._fwdref` submodule to support arbitrary subscription of **forward reference proxies** (i.e., subclasses dynamically created and returned by the `make_forwardref_indexable_subtype()` factory function exported by this submodule), enabling these proxies to transparently proxy forward references to user-defined generics. * Finalizes internal commentary on how to *eventually* implement support for recursive type hints. It's `RecursionError` or bust for @beartype. (*Unassailable assuagement of suave management!*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit begins migrating the PEP 563-specific stringified type hint resolution implemented by our public (albeit undocumented) `beartype.peps.resolve_pep563()` resolver to a new private `beartype._check.forward.fwdhint.resolve_hint()` resolver, implementing a PEP 563-agnostic stringified type hint resolution. Needless to say, nothing works, nothing is tested, and the sky is falling. Praise the sky! (*Sunless honey of a timeless tummy!*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit continues migrating the PEP 563-specific stringified type hint resolution implemented by our public (albeit undocumented) `beartype.peps.resolve_pep563()` resolver to a new private `beartype._check.forward.fwdhint.resolve_hint()` resolver, implementing a PEP 563-agnostic stringified type hint resolution. Needless to say, nothing works, nothing is tested, and the sky is falling. Praise the sky! (*Baseless bastion of a best-basted lambasting!*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit continues migrating the PEP 563-specific stringified type hint resolution implemented by our public (albeit undocumented) `beartype.peps.resolve_pep563()` resolver to a new private `beartype._check.forward.fwdhint.resolve_hint()` resolver, implementing a PEP 563-agnostic stringified type hint resolution. Needless to say, nothing works, nothing is tested, and the sky is falling. Praise the sky! (*Rancid transport is an acid sport!*)
This commit is the next in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit continues migrating the PEP 563-specific stringified type hint resolution implemented by our public (albeit undocumented) `beartype.peps.resolve_pep563()` resolver to a new private `beartype._check.forward.fwdhint.resolve_hint()` resolver, implementing a PEP 563-agnostic stringified type hint resolution. Needless to say, nothing works, nothing is tested, and the sky is falling. Praise the sky! (*Withering alliance within a dancing dalliance!*)
This commit is the last in a commit chain generalizing @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward references** (i.e., references to user-defined *objects* that have yet to be defined in the current lexical scope, regardless of whether those objects are types or not), en-route to resolving feature request #226 kindly submitted by Oxford Aspiring Algorithms Aficionado (AAA) @eohomegrownapps (Euan Ong). Specifically, this commit finishes migrating the PEP 563-specific stringified type hint resolution implemented by our public (albeit undocumented) `beartype.peps.resolve_pep563()` resolver to a new private `beartype._check.forward.fwdhint.resolve_hint()` resolver, implementing a PEP 563-agnostic stringified type hint resolution. Needless to say, everything works, everything is tested, and even the Sun itself has now return to its comfortable orbit. Praise the Sun! In theory, this resolution should also suffice to resolve *all* other outstanding issues pertaining to both forward hints and PEP 563. In practice, everything else that is broken is probably still broken. Let us exhaustedly pass out already, GitHub! (*Pneumatic phlegmatics!*)
omg! OMG! OMGGGGG!!! @TeamSpen210: Your brilliant five-sentence abstract on the nature of Python vis-a-vis forward references actually worked! It actually worked, bro! Punch the air like you just don't care, 'cause that's what we're doin'. Naturally, your "simpler approach" was horrifying. My once-beautific bald face is crinkled with wrinkles, my twitchy eye is even more twitchy, and our cat won't stop pacing the room. I spent the entirety of my vacation doing this – and I don't even need this in production code. It just became a titanic struggle of @leycec versus @beartype versus Python: a three-way untelevised cage match that could only end with one man hurtling fifty feet onto another man lying prostrate on a table perched on top of another man curled in the fetal position while bleeding out live on GitHub.
Seriously. I've got five hundred line comments devsplaining single one-liners. I've got 100KB of incomprehensible pure-Python isolated to four new submodules sprawled across a new subpackage devoted exclusively to inchoate madness. I've got abstract base classes, abstract metaclasses, dynamic type factories, dunder methods on sunder methods on abstract cached properties. I've got friggin' metaclass I don't even know how any of this works anymore. I've already jettisoned all knowledge pertaining to this topic so I can just sleep tonight without fitfully crying out in the night for my childhood stuffed octopus. I wish to awaken from the real-world nightmare I have unwittingly exposed myself to. It turns out Bloodborne is more a state of mind rather than a mere video game. Forward references: do not go there, lest this animated reenactment of @leycec's life corrupt your once-pure essence. I'm pretty sure (but not certain) that I may have inadvertently summoned the Eldritch She-God Shub-Niggurath, The Black Goat of the Woods with a Thousand Young, during the resolution of this feature request. I apologize, humanity. I'm so sorry! I didn't mean to open that portal. It just opened of its own accord while the guard cat was indolently passed out last night. |
This commit generalizes @beartype's support for forward references from **type forward references** (i.e., references to user-defined *types* that have yet to be defined in the current lexical scope) to **arbitrary forward self-references** (i.e., references to user-defined instances of the currently decorated class – which has yet to be defined in the current lexical scope and thus requires forward references), resolving feature request #217 kindly submitted by Google X JAX "Acronym Maestro" @patrick-kidger (Patrick Kidger). Specifically, this commit unit tests that the prior commit chain resolving feature request #226 "Postponed evaluation of non-referential hints" also perfectly resolved this adjacent feature request as well. Laziness prevails. (*Prevalent multivalence predominates!*)
@beartype 0.16.0 **[**codename: _Super Unsexy Stabilization Asinine Force (SUSAF)_**]** boringly stabilizes everything unstable about @beartype that made your coworker who only wears skinny jeans while squinting whenever you mention @beartype stop doing those things. Fix everything bad, SUSAF! ```bash python3 -m pip install --upgrade beartype ``` @beartype 0.16.0 mostly introduces nothing new. Instead, @beartype 0.16.0 just fixes everything that's been broken for several years. We all tried to pretend that those things worked. I'm grateful for that. You were willing to look the other way while @beartype insanely stumbled around with its paws up in the air. Now, @beartype *finally* makes good on one or two of its historical promises. To introduce our SUSAF feature list, @beartype presents... *super unsexy anime ugly man Rock Lee!* ![](https://media.tenor.com/JUUbWGzszucAAAAC/rock-lee-thumbs-up.gif) <sup>Seriously. Those eyes. Those eyebrows. That bowl cut. *Seriously.*</sup> All of these things and more now fully work as intended: * **Complex forward references** like `"List[MuhClass[str]]"`. *Party!* 🥳 * **PEP 563** via `from __future__ import annotations`. *Never actually do this.* 🕺 * **Class properties** via `@classmethod` + `@property`. *Never actually do this, either.* 💃 * **Nested `typing.NewType()` objects.** *Compose those new types together, because you can!* 😑 * **Truncated `beartype.claw` warnings.** *`beartype.claw` now tries to be quieter, but fails!* 😮💨 * **`beartype.claw`** + **`python -m {your_package}.{your_module}`.** *It just works! Maybe!* 😃 * **Immutable types** like `@beartype` + `enum.StrEnum`. *You know somebody wanted this!* 🐳 @beartype now supports basically *everything*. @beartype should no longer raise decoration-time exceptions about unsupported type hints, because all type hints should now be supported. Because we just used weasel words like "basically" and "should," you "basically" "should" not trust anything I just wrote. Instead, please throw `beartype.claw.beartype_this_package()` against the bug-stained windshield of your favourite codebase. [Let us know if @beartype is still broke af for your use case](https://github.com/beartype/beartype/issues). Previously, everyone blacklisted `@beartype` from decorating one or more problematic classes, methods, or functions with the standard `@no_type_check` decorator. Now, you no longer need to that. Unleash the bear. Let @beartype claw its disturbingly hungry way through your ~~disconcerting infestation of bugs~~ bug-free codebase. Taiko drum roll, please. ![](https://media.tenor.com/sbBfh5mhjlEAAAAC/kumamoto-kumamon.gif) <sup>Above: @leycec in a troubled moment during the 0.16.0 release cycle.</sup> ## GitHub Sponsors: You Make the Hard Work Less Work But first... a huge thank you to @beartype's [growing retinue of one-time and recurring sponsors](https://github.com/sponsors/leycec)! I'm incredibly touched in a good way by everyone's outpouring of hard-earned biosecurity tickets. You know who you are... but nobody else did. *Until now.* @beartype sponsors, please line up to receive your complimentary sugar-free Bear Claw of Undulating Adulation & Unending Congratulations: * @posita! * @gabrieldemarmiesse! * @skeggse! * @langfield! * @ArneBachmannDLR! * @great-work-told-is! * @dertilo! * @zero-guard! * @polyxemus! * @albanie! * @KyleKing! I also kinda feed bad. I *do* intend to deliver a high-quality QA product of blood, sweat, tears, memes, and fun – but I also frequently drop the ball and fall short of that lofty goal. Moreover, money is increasingly hard for many to find in 2023. The post-scarcity future that Ian Banks promised that I personally would live to see [in depressing sci-fi books about Culture – the mythological utopian society that I will probably never live to see](https://en.wikipedia.org/wiki/Culture_series) – has never seemed further away. Thank you. So much. ![](https://media.tenor.com/C6NQJ4A_P_EAAAAC/ichiya.gif) <sup>The true form of @beartype is revealed – *and also thanks you.*</sup> Up next... @beartype 0.16.0 *did* actually do something novel. Surprise! It's: ## `${BEARTYPE_IS_COLOR}`: Make the Bad Color Go Away @beartype 0.16.0 introduces our **first official shell environment variable:** [`${BEARTYPE_IS_COLOR}`](https://beartype.readthedocs.io/en/latest/api_decor/#beartype-is-color). Through the power of `${BEARTYPE_IS_COLOR}`, you too can now enforce a global colour policy by externally configuring [our popular `BeartypeConf.is_color` option](https://beartype.readthedocs.io/en/latest/api_decor/#beartype.BeartypeConf.is_color) from the command line. As with `is_color`, `${BEARTYPE_IS_COLOR}` is a tri-state boolean with three possible string values: * `BEARTYPE_IS_COLOR='True'`, forcefully instantiating all beartype configurations across all Python processes with the `is_color=True` parameter. * `BEARTYPE_IS_COLOR='False'`, forcefully instantiating all beartype configurations across all Python processes with the `is_color=False` parameter. * `BEARTYPE_IS_COLOR='None'`, forcefully instantiating all beartype configurations across all Python processes with the is_color=None parameter. Force beartype to obey your unthinking hatred of the colour spectrum. You can’t be wrong! ```bash BEARTYPE_IS_COLOR=False python3 -m monochrome_retro_app.its_srsly_cool ``` ![](https://media.tenor.com/BOdfO9q6wYsAAAAC/dodoria-dragon-ball.gif) <sup>When @beartype makes your eyes bleed, call `${BEARTYPE_IS_COLOR}`.<sup> ## Forward References: This Day All Circular Imports Die Previously, @beartype only supported **simple forward references** consisting of one or more `"."`-delimited Python identifiers like `"MuhClass"` and `muh_package.muh_module.MuhClass`. Now, @beartype *finally* supports **complex forward references** consisting of arbitrary combinations of references to classes that have yet to be defined both subscripted by and subscripting type hint factories. In other words, forward references just work now. Bask in the ruthless complexity of @beartype now fully resolving a **subscripted generic type alias forward reference** (i.e., a stringified type hint referring to a global attribute that has yet to be defined whose value is a subscripted generic that also has yet to be defined). It hurts – but @beartype is here to help: ```python from beartype import beartype from beartype.typing import Generic, List, TypeVar @beartype def this_is_fine(still_fine: 'WonkyTypeAlias') -> 'List[WonkyTypeAlias]': return [still_fine] # Type variable. Once upon a time you dressed so fine, @beartype. T = TypeVar('T') # Generic. Threw the bums a dime in your prime, didn't you, @beartype? class WonkyGeneric(Generic[T]): pass # Type alias. People call say, "Beware, doll. You're bound to fall, @beartype." WonkyTypeAlias = WonkyGeneric[int] # Prints: # [<__main__.WonkyGeneric object at 0x7f27e21b1f50>] print(this_is_fine(WonkyGeneric()) ``` Like a rolling stone, @beartype now brings it all back home. Sadly, none of this is free. There are now space and time costs associated with forward references that you should be aware of. For most codebases, these costs will be negligible and thus ignorable. For some codebases, however, <sup>...especially codebases enabling PEP 563 via `from __future__ import annotations`</sup>, these costs could gradually become non-negligible and thus unignorable. Let's unpack what *exactly* is happening above. 1. First, @beartype notices that your `this_is_fine()` function is annotated by two **stringified type hints** (i.e., type hints that are strings describing standard type hints rather than standard type hints). 1. Next, @beartype de-stringifies these stringified type hints as follows. For each stringified type hint: 1. First, @beartype attempts to replace each stringified attribute in that hint with a previously defined attribute of the same name in the current scope. Since the `List` attribute has been previously imported, @beartype replaces `List` in the return annotation with the previously imported `beartype.typing.List` attribute. Now: * The signature of `this_is_fine()` resembles `def this_is_fine(still_fine: 'WonkyTypeAlias') -> List['WonkyTypeAlias']:`. The only remaining stringified attributes refer to `'WonkyTypeAlias'`, which has yet to be defined. 1. Next, @beartype replaces each remaining stringified attribute in that hint with a **forward reference proxy** (i.e., a type dynamically created by @beartype unique to that attribute). Forward reference proxies transparently delay the importation of the *actual* attributes they refer to until those attributes are actually needed – under the optimistic assumption that those attributes will be defined (and thus importable) at that point. Forward reference proxies are internal (and thus private) to @beartype. Since @beartype dynamically fabricates one forward reference proxy type for each unresolved stringified attribute in your codebase, forward reference proxies have strange classnames like `_BeartypeForwardRefIndexable`. (It's best not to think too hard about this. @leycec sure didn't.) Now: * The signature of `this_is_fine()` resembles `def this_is_fine(still_fine: _BeartypeForwardRefIndexable('WonkyTypeAlias')) -> List[_BeartypeForwardRefIndexable('WonkyTypeAlias')]:`. No stringified attributes remain. All previously stringified type hints have now been replaced by standard type hints. Well, *sort* of standard type hints. I mean, we're all just squinting at this point. 1. Next, @beartype wraps your `this_is_fine()` function with a dynamically generated wrapper function performing runtime type-checking. B-b-but... how does @beartype even do that!?!? After all, neither the `WoknyGeneric` nor `WonkyTypeAlias` global have been defined yet. Ah-ha! Here is where the magic and the madness happens. Remember those forward reference proxies like `_BeartypeForwardRefIndexable('WonkyTypeAlias')` that @beartype previously injected into your type hints? Right. Those now do something. Each forward reference proxy now pretends that it is a normal class. From this point on, as far as @beartype and everything else in the Python ecosystem is concerned, forward reference proxies are just normal classes. You type-check them like you would any normal class (i.e., by passing them as the second argument to the `isinstance()` builtin). @beartype's code generation algorithm doesn't know any better. Ignorance is bliss when you are tired, it's Friday night, and you just want to play five hours more of **Baldur's Gate 3** already. 1. Next, you define both your `WonkyGeneric` and `WonkyTypeAlias` global attributes. 1. Last, you call your `this_is_fine()` function. Magic and madness erupt. At last! Remember those forward reference proxies? They're *baaaaaack*. They finally do something. The type-checking code that @beartype previously wrapped `this_is_fine()` with kicks in by: 1. Passing each forward reference proxy as the second argument to the `isinstance()` builtin, which then... 1. Implicitly calls the subversive `__instancecheck__()` dunder method defined by the private metaclass `_BeartypeForwardRefMeta` of that forward reference proxy, which then... 1. Explicitly calls the `is_instance()` method defined by that forward reference proxy, which then... 1. Dynamically imports the global attribute `WonkyTypeAlias` from your module, which has now been defined. 1. Detects that that attribute is actually a subscripted generic `WonkyGeneric[int]` rather than an actual type. 1. Reduces that subscripted generic to the unsubscripted generic `WonkyGeneric`, which is an actual type. 1. Internally caches that type inside that forward reference proxy (to optimize subsequent type-checks). 1. Performs the desired type-check by passing that type as the second argument to the `isinstance()` builtin. 1. B-b-but... what is even going on anymore? I myself hardly know. > "My head just exploded and I hurt all over. Please. Stop talking." – > you, probably This is an understandable response when @leycec starts talking. The laborious point I am trying and failing to make here is that forward references now incur costs. Ignoring the ridiculous explosion in code complexity, there are now unavoidable space and time costs here. Types are being cached all over. `eval()` is being called all over. Dynamic forward reference proxy classes are being instantiated all over. @beartype optimizes this as much as feasible, because that's why we're here. Realistically, though? There's just *soooo* much magical dynamism here. @beartype can only safely optimize so much. You don't even want to know what @beartype does for fully-qualified forward references like `'muh_package.muh_module.MuhGeneric[T]'`. Let us just say that it is Trash Day, your trash bins are now overflowing with stinky refuse, and Python's garbage collector is parked out front eviscerating the steaming mountain of short-lived objects in generation 0 that you have inflicted upon it. Sometimes, you just need to forward reference. That's fine. @beartype is now here for you. Just try to keep it low-key. KK? Oh – and here's what [@leycec's idealistic and better-adjusted younger self had to say about this](#226 (comment)): **omg! OMG! OMGGGGG!!!** @TeamSpen210: [Your brilliant five-sentence abstract on the nature of Python vis-a-vis forward references](#226 (comment)) *actually* worked! It *actually* worked, bro! Punch the air like you just don't care, 'cause that's what we're doin'. ![do it like this](https://media.tenor.com/hV03QzfXrtEAAAAC/hatsune-priconne.gif) Naturally, your "simpler approach" was horrifying. My once-beautific bald face is crinkled with wrinkles, my twitchy eye is even more twitchy, and our cat won't stop pacing the room. I spent the entirety of my vacation doing this – and I don't even *need* this in production code. It just became a titanic struggle of @leycec versus @beartype versus Python: a three-way untelevised cage match that could only end with one man hurtling fifty feet onto another man lying prostrate on a table perched on top of another man curled in the fetal position while bleeding out live on GitHub. ![mankind is sad](https://media.tenor.com/t-Tw6ma_jxIAAAAC/undertaker-mankind.gif) <sup>...dat the best u can do, python?</sup> ![wat](https://media.tenor.com/T1E389Up3RgAAAAC/jeff-hardy-whisper-in-the-wind.gif) <sup>*wat is even happening here*</sup> ![uh gods](https://media.tenor.com/3U1flZRg0f8AAAAC/adam-cole-johnny-gargano.gif) <sup>When your masterplan only ends in tragedy for everybody.</sup> Seriously. I've got five hundred line comments devsplaining single one-liners. I've got 100KB of incomprehensible pure-Python isolated to four new submodules sprawled across a new subpackage devoted exclusively to inchoate madness. I've got abstract base classes, abstract metaclasses, dynamic type factories, dunder methods on sunder methods on abstract cached properties. I've got friggin' metaclass `__getattr__()` methods performing deferred module attribute importation via dynamic class attribute lookups of attributes that don't actually physically exist anywhere. I've got two whole `dict.__missing__()` implementations that don't even really have anything to do with one another. *This is inchoate madness.* I don't even know how any of this works anymore. I've already jettisoned all knowledge pertaining to this topic so I can just sleep tonight without fitfully crying out in the night for my childhood stuffed octopus. I wish to awaken from the real-world nightmare I have unwittingly exposed myself to. It turns out *Bloodborne* is more a state of mind rather than a mere video game. ![who knew](https://media.tenor.com/-AMHQLsgQXcAAAAd/bloodborne-werewolf.gif) <sup>Python. *It do be like that.*</sup> Forward references: do not go there, lest this animated reenactment of @leycec's life corrupt your once-pure essence. I'm pretty sure (but not certain) that I may have inadvertently summoned the Eldritch She-God Shub-Niggurath, The Black Goat of the Woods with a Thousand Young, during the resolution of this feature request. I apologize, humanity. I'm so sorry! I didn't *mean* to open that portal. It just opened of its own accord while the guard cat was indolently passed out last night. It happened. @leycec remembers. ![](https://media.tenor.com/VPF4zO74gQcAAAAC/full-kumamoto.gif) <sup>Kumamoto Bear remembers, too.</sup> ## PEP 563: You Still Shouldn't Do It. Now, You Can. And then there was PEP 563 via `from __future__ import annotations` – the dread PEP whose name shall not be spake that we just spake. We fundamentally reimplemented our *entire* PEP 563 pipeline in terms of our forward resolution mechanism from Hell. (See: above.) From @beartype's perspective, PEP 563 basically no longer exists. @beartype no longer particularly cares whether you explicitly stringify your type hint likes `'List[WonkyTypeAlias]'` *or* implicitly stringify your type hints via `from __future__ import annotations`. The end result is the exact same: stringified type hints. Do we all now understand why PEP 563 is bad? We do. But let us tiresomely enumerate the ways, anyway: * PEP 563 was supposed to **increase efficiency.** * But we have just demonstrated that resolving stringified type hints **reduces efficiency** – often, dramatically. * Therefore, we conclude that PEP 563 actually **reduces efficiency** – often, dramatically. Here. We're gonna give it to you straight. If you enable PEP 563 across only a small number of modules, it's unlikely that you will see a measurable performance hit. If you enable PEP 563 across **your entire friggin' codebase**, however? Yeah. Expect those performance hits. Your apps will startup slower, run slower even after they spin up, and possibly even catastrophically fail in pernicious edge cases. Don't be that guy. Don't enable PEP 563. *Not even once.* ![](https://media.tenor.com/1Nipv-735QIAAAAC/pesci-pesci-jjba.gif) <sup>Would you trust a man whose neck is also his chin? @beartype would.</sup> ## Plum: It Just Works Better @wesselb's [magnum opus to @beartype-based multiple dispatch](https://github.com/beartype/plum) now works better. Notably: * Plum users may now safely enable PEP 563 via `from __future__ import annotations`. I mean, you *shouldn't.* But you can. Maybe. But... yeah. I still wouldn't. * Multiple dispatch across PEP 586-compliant `typing.Literal[...]` type hints now works considerably better. @PhilipVinc: *you know who you are.* ![](https://media.tenor.com/ZTNuWTVibG8AAAAC/howls-moving.gif) <sup>Plum: even the poor little doggo agrees</sup> ## Shoutouts to the Beasts in Back Greets to @empyrealapp, @PhilipVinc, @rlkelly, @KyleKing, @RomainBrault, @tolomea, @skeggse, @alexander-c-b, @gabrieldemarmiesse, @machow. You know who you are. You are... *awesome!* ![](https://media.tenor.com/4xVqzVUWz4cAAAAC/kinnikuman-suguru-kinniku.gif) <sup>i luv u</sup> It was SUSAF. It was... **@beartype 0.16.0.**
Thanks for developing a really cool library :)
Just one issue I found when using forward references with beartype: the following code snippets (which statically typecheck with MyPy) seem to throw BearType exceptions:
throws
BeartypeDecorHintForwardRefException: Forward reference "__main__.List[MyCls]" syntactically invalid as module attribute name.
and
throws
BeartypeDecorHintForwardRefException: Forward reference "__main__.MyCls[int]" syntactically invalid as module attribute name.
(P.S. do you have an ETA on this feature #43 or is there a hacky workaround I could use for the time being?)
The text was updated successfully, but these errors were encountered: