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
[Bug] utilpepget.get_hint_pep_args
returns incorrect value for typing.Callables
#137
Comments
Curse you yet again, ...ahem. So, I may have gotten a bit carried away there. But I did so for a justifiably valid reason. And that reason is this: the Nothing that The Empty Tuple Type HintSo, there's this totally Bizarro World type hint called the "empty tuple type hint" specified with magic syntactic sugar like But that's also the only means of annotating a fixed-length tuple of length 0. Since there are valid reasons for wanting to do that, PEP 484 explicitly supports that:
Here's where things get ugly. # PEP 484-style empty tuple type hint. This makes sense.
>>> from typing import Tuple
>>> tuple0_pep484 = Tuple[()]
>>> tuple0_pep484.__args__
((),)
# So, @beartype correctly infers the arguments of an
# PEP 484-style empty tuple type hint to be the empty tuple.
# This makes sense, too.
>>> from beartype._util.hint.pep.utilpepget import get_hint_pep_args
>>> get_hint_pep_args(tuple0_pep484)
((),)
# typing.get_args() also correctly infers the same arguments.
# So far, so good.
>>> from typing import get_args
>>> get_args(tuple0_pep484)
((),)
# And things take a sudden turn for the worse. Here's a
# PEP 585-style empty tuple type hint. This hint incorrectly
# destroys its arguments, because the C-based implementation
# of the "types.GenericAlias" superclass is broken. Not kidding.
>>> tuple0_pep585 = Tuple[()]
>>> tuple0_pep585.__args__
()
# But things get better for a bit. @beartype internally detects
# this edge case and restores portability by pretending that
# PEP 484- and 585-style empty tuple type hints have the
# same arguments.
>>> get_hint_pep_args(tuple0_pep585)
((),)
# Then things get worse again. typing.get_args() doesn't
# know that "types.GenericAlias" is broken, so it silently
# trusts "types.GenericAlias" by returning its broken
# "__args__" tuple sight unseen.
>>> get_args(tuple0_pep585)
() But Wait, More BadnessOh – and there's yet another reason to avoid def get_args(tp):
...
res = (list(res[:-1]), res[-1]) # <-- this is bad, bro @beartype internally memoizes everything it does for space and time efficiency. We can't do that if we start passing lists around. Ergo, we still need to handle this ourselves. I sigh expansively. 😮💨 And... that's just two mind-bending edge cases. Python 3.7's Please God Just Make Callables Work, AlreadyI feel your growing migraine and kindly sympathize as only a fellow runtime typing enthusiast can. @beartype currently fails to deeply type-check >>> from typing import Callable
>>> Callable[[], int].__args__
(int,)
>>> from collections.abc import Callable as CallableABC
>>> CallableABC[[], int].__args__
(int,) I sigh wearily with exhaustion exceeding the finite bounds of string theory's eleventh dimension. The only saving grace here is that it's trivial for @beartype to explicitly detect and handle that edge case (e.g., with the same hacky logic that Thank You So Much, @tlambert03Allow me to conclude by effusively thanking you for all your tremendous volunteerism, @tlambert03. You've been a huge help over the past few days. The QA community applauds your valiant bravery. This is rough stuff. I really appreciate you toughing this out on the front lines with me. Your herculean efforts will be vindicated when I merge your awe-inspiring PR later this week. Together, we will vanquish the darkness by the power of type hint arithmetic. |
Hmm... In reflection, it's probably preferable for Sure, flattened tuples are a bit oddball from the semantic perspective – but they're quite a bit easier to deal with from the pragmatic perspective. Does that make sense? Is there something I'm fundamentally missing that requires That said...
|
This commit is the first in a commit chain improving our private `beartype._util.hint.pep.utilpepget.get_hint_pep_args()` getter to **deflatten** (i.e., restore to a facsimile of their original form) `Callable[...]` type hints passed to that getter, en-route to resolving issue #137 kindly submitted by Harvard microscopist and all-around typing math guru @tlambert03 (Talley Lambert), required as a mandatory dependency of pull request #136 also kindly submitted by @tlambert03. Specifically, this commit lightly refactors that getter in preparation for subsequent surgery as well as detailing internal improvements needed elsewhere for this to fully behave as expected. (*Long-tallied talons!*)
So. It begins. |
Actually... I hope you don't mind if I close this issue out on you. The current behaviour of My eternal gratitude for all your extraordinary outpouring of enthusiasm for this. So excited, peeps! 😄 |
yep, no problem! |
typing.get_args special cases both
Annotated
and callable classes:but
utilpepget.get_hint_pep_args
only checks for__args__
, which leads to the following behavior:Is there any reason not to try to use
typing.get_args
when available? (Not entirely sure about all the potential back-porting and other compatibility issues here)running into this in #136
The text was updated successfully, but these errors were encountered: