Skip to content
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

Closed
euanong opened this issue Mar 25, 2023 · 14 comments
Closed

Comments

@euanong
Copy link

euanong commented Mar 25, 2023

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:

from dataclasses import dataclass
from typing import List, TypeVar
from beartype import beartype

@beartype
def my_fn(xs: "List[MyCls]"):
    return xs[0]

@dataclass
class MyCls:
    value: int

throws

BeartypeDecorHintForwardRefException: Forward reference "__main__.List[MyCls]" syntactically invalid as module attribute name.

and

from dataclasses import dataclass
from typing import List, TypeVar, Generic
from beartype import beartype

T = TypeVar("T")

@beartype
def my_fn(x: "MyCls[int]"):
    return x.value

@dataclass
class MyCls(Generic[T]):
    value: T

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?)

@leycec leycec changed the title Forward references and generics don't play nicely together [Feature Request] Postponed evaluation of subscripted generics Mar 26, 2023
@leycec leycec changed the title [Feature Request] Postponed evaluation of subscripted generics [Feature Request] Postponed evaluation of non-self-referential type hints Mar 26, 2023
@leycec
Copy link
Member

leycec commented Mar 26, 2023

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 @beartype to resolve non-trivial forward references like this, because @beartype needs to dynamically generate type-checking Python code at runtime for attributes that have yet to be defined. In this case, the stringified type hint "List[MyCls]" references:

  • An attribute List that already has been defined. No problem, @beartype!
  • An attribute MyCls that has yet to be defined. This is problem, @beartype.

How Can I Just Make This Work Now?

Thankfully, @beartype already basically supports exactly what you want. Just replace the string "List[MyCls]" in the above example with List["MyCls"]. That is to say, stringify only the exact class that has yet to be defined: e.g.,

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 typing.TYPE_CHECKING type hint. typing.TYPE_CHECKING is the Ultimate Power™. When you want to do both static and runtime type-checking, typing.TYPE_CHECKING lets you conditionally create different type hints at static and runtime: e.g.,

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 typing.TYPE_CHECKING to:

  • Preserve your desired MyCls[int] for analysis by mypy.
  • Reduce that to simply MyCls for analysis by @beartype. Nothing is lost, since @beartype already internally ignores child type hints like [int] on user-defined subscripted generics. They convey no meaningful semantics at runtime. Right? Right!

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, @beartype will need to (in order):

  1. Internally implement a full-blown forward reference parser – probably using precompiled (but still slow) regular expressions to parse stringified type hints like "List[MyCls]" into their constituent Python identifiers (e.g., "List", "MyCls").
  2. For each such identifier:
    1. Decide whether an attribute with that name has already been defined.
    2. If already defined like "List", no problem!
    3. If undefined like "MyCls", problem! Fix this by globally replacing in that type hint all instances of that name with __beartypistry[{name}] (e.g., __beartypistry['MyCls'] in this case). As that code snippet suggests, __beartypistry is a @beartype-specific caching dictionary that drives our existing forward reference implementation.

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 beartype 0.14 release cycle – to be released sometime later this Summer, surely. Would @leycec lie? 😓

@TeamSpen210
Copy link

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 door objects instead.

@euanong
Copy link
Author

euanong commented Mar 26, 2023

@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 (I miss typeclasses ;-;) with very extensive use of (nested) forward references... but great to know this will be patched eventually :)

Nothing is lost, since https://github.com/beartype already internally ignores child type hints like [int] on user-defined subscripted generics. They convey no meaningful semantics at runtime. Right? Right!

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 (?)

(P.S. do you have an ETA on this feature #43 or is there a hacky workaround I could use for the time being?)

(Also, this was referring to issue #43, not this feature :) )

@leycec
Copy link
Member

leycec commented Mar 26, 2023

Unfortunately, I'm not sure how scalable this temporary fix will be...

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. 🌞

it would be really awesome to have @beartype inspect user-defined subscripted generics to determine (in most cases, with high probability) whether they typecheck...

Yes! I'd love to have that, too. Sadly...

I imagine, though, this would require a little too much python hackery / performance cost of tracking typevars to be palatable (?)

...heh. That's the concern, really. To do this sanely, @beartype will need to begin injecting @beartype-specific instance variables into each instance of a @beartype-decorated generic. Exactly as you suggest, these variables would then track all prior TypeVar associations across the lifetime of that instance. For instance, hyuk, hyuk @beartype might inject:

  • A new __beartype_typevar_to_type dictionary into each object dictionary (i.e., obj.__dict__). __beartype_typevar_to_type would map:
    • From each TypeVar object subscripting that generic (e.g., the T = TypeVar('T') in class MyCls(T))...
    • To either:
      • If a corresponding type has already been associated with that TypeVar of that generic (e.g., the int in my_instance = MyCls[int]), that type.
      • Else, None.

@beartype will then need to dynamically generic type-checking that looks up types in the __beartype_typevar_to_type dictionary.

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. 🙀

@leycec
Copy link
Member

leycec commented Mar 26, 2023

@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...

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 door objects instead.

...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:

  • Let us define these proxy objects as instances of a new BeartypeForwardRef class in a new beartype._decor._cache.cacheref submodule.
  • The definition of BeartypeForwardRef should resemble:
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 __instancecheck__() shenanigans, now @beartype can just treat BeartypeForwardRef instances like standard types. @beartype no longer needs to treat forward references and standard types as fundamentally different things. If fortune favours the brave, then simple constraints like the following should now hold:

>>> isinstance(MyCls(), BeartypeForwardRef('MyCls'))
True
>>> isinstance(MyCls[int](), BeartypeForwardRef('MyCls')[int])
True

This should substantially streamline @beartype's code generation. Minds are being blown. 💥 🤯

@st--
Copy link

st-- commented Apr 17, 2023

I'm encountering a similar issue, but in a different setup, where the exception is
beartype.roar.BeartypeDecorHintForwardRefException: Forward reference "gpjax.kernels.approximations.rff.Optional[List[int]]" syntactically invalid as module attribute name.
and the issue is a base class in one file

@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 active_dims. (If I copy&paste the definition from the base class around, this error is fixed, but of course that does away with all the conveniences of inheriting fields...)

Would it be helpful for you to have a MRE of this, too? (Asking first because I'm using beartype with jaxtyping and jaxtyping.install_import_hook, and Module and static_field are custom as well so it'll be a bit more work to disentangle it into a standalone example.)

@leycec
Copy link
Member

leycec commented Apr 18, 2023

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 @dataclass use case, though. From the little that I can tell, there are no stringified type hints and thus no forward references in your example; there are only standard type hints (e.g., Optional[List[int]]). In fact, this painfully unhelpful MLE behaves as expected:

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, jaxtyping, jax-dataclasses, and/or PEP 563 (i.e., from __future__ import annotations).

There's a lot of third-party intensity going on here. I didn't even know that jaxtyping had its own import hook, for example. That's... possibly not good. I mean, that's good for jaxtyping. Clearly. But import hooks have a tendency to play poorly with other import hooks.

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.

totally ripped
kinda afraid to ask what the end user codebase is in this suspicious metaphor

@st--
Copy link

st-- commented Apr 18, 2023

It's the from __future__ import annotations. MRE:

File mre/__init__.py:

from jaxtyping import install_import_hook

with install_import_hook("mre", "beartype.beartype"):
    from .base import *
    from .rff import *

File mre/base.py:

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 mre/rff.py:

#from __future__ import annotations
from dataclasses import dataclass

from .base import AbstractKernel

@dataclass
class RFF(AbstractKernel):
    pass

Then import mre gives the following error:

BeartypeDecorHintForwardRefException: Forward reference "mre2.rff.Optional[List[int]]" syntactically invalid as module attribute name.

If you comment out the from __future__ import annotations in the base.py, the error disappears.

I previously got this error:

BeartypePep563Exception: RFF.__init__() parent lexical scope RFF() not found on call stack.

when both modules had the from __future__ import annotations, but I stripped away too much and now that one I'm not sure what it was caused by...

(st-- is a legacy user name. You can't have trailing dashes anymore, and unfortunately one implication is that I can't have an st--.github.io GitHub pages. It also means I can't contribute to Google open-source packages such as TensorFlow (or JAX) because their CLA validator refuses to accept my username... How do you make the font size smaller?)

@leycec
Copy link
Member

leycec commented Apr 19, 2023

Ah-ha! So. The horrifying truth reveals itself at last. from __future__ import annotations, we meet again. The years pass. The seasons turn. The seas swell. Yet PEP 563 still sucks.

Thanks so much for that awesome MRE, @st--. Since I kinda need to push out a new beartype 0.13.2 release tonight, I lack volunteer time to run your MRE through the gauntlet like I should. But all's not for naught! I suspect I know how to make everything work again:

# 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 mre/__init__.py by from __future__ import annotations. If a miracle erupts, I can explain why. Else, I hang my bald head and weep salty tears at midnight.

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. 🐻

@st--
Copy link

st-- commented Apr 19, 2023

I've tried it out but I'm afraid adding the future annotations import to __init__.py doesn't fix the issue. Only adding it to all the files resolves that [if I can reproduce the error I got then, I'll share that], or removing it everywhere..

@leycec
Copy link
Member

leycec commented Apr 19, 2023

@st--: I summon thee to issue #233, where we will avoid bludgeoning these other hapless victims with unrelated pabulum that can only enrage and disappoint them.

We now return to our regularly scheduled issue.

@leycec
Copy link
Member

leycec commented Jul 19, 2023

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.

leycec added a commit that referenced this issue Aug 11, 2023
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?*)
@leycec
Copy link
Member

leycec commented Aug 11, 2023

jojo pucci wut

leycec added a commit that referenced this issue Aug 12, 2023
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!*)
leycec added a commit that referenced this issue Aug 15, 2023
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?*)
leycec added a commit that referenced this issue Aug 16, 2023
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!*)
leycec added a commit that referenced this issue Aug 17, 2023
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?*)
leycec added a commit that referenced this issue Aug 18, 2023
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!*)
leycec added a commit that referenced this issue Aug 19, 2023
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!*)
leycec added a commit that referenced this issue Aug 22, 2023
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!*)
leycec added a commit that referenced this issue Aug 23, 2023
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!*)
leycec added a commit that referenced this issue Aug 24, 2023
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!*)
leycec added a commit that referenced this issue Aug 25, 2023
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!*)
leycec added a commit that referenced this issue Aug 26, 2023
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!*)
@leycec
Copy link
Member

leycec commented Aug 26, 2023

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'.

do it like this

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
...dat the best u can do, python?

wat
wat is even happening here

uh gods
When your masterplan only ends in tragedy for everybody.

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
Python. It do be like that.

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.

@leycec leycec closed this as completed Aug 26, 2023
leycec added a commit that referenced this issue Aug 29, 2023
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!*)
leycec added a commit that referenced this issue Sep 16, 2023
@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.**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants