Bug report
While looking at the typing module I noticed that _tp_cache can run the function it wraps twice.
_tp_cache wraps __getitem__ and similar methods with an lru_cache. Some of the arguments passed to those methods can be unhashable, so it calls the cached version inside a try and falls back to calling the original function when a TypeError shows up, on the assumption that the TypeError came from trying to hash an unhashable argument.
The problem is that the wrapped function can also raise TypeError on its own, for reasons that have nothing to do with hashing. When that happens with hashable arguments, the function runs once inside the cached call and raises, the except TypeError catches it, and then the fallback runs the same function a second time before the error finally propagates.
A small reproduction:
import typing
calls = []
@typing._tp_cache
def f(x):
calls.append(x)
raise TypeError("unrelated to hashability")
try:
f("value")
except TypeError:
pass
print(calls) # ['value', 'value'] -- f ran twice
It is also reachable through the public API. Any subscription whose cached __getitem__ validates its arguments and raises TypeError for hashable arguments hits this:
import typing
typing.List[int, str] # too many arguments
typing.Optional[int, str] # requires a single type
typing.Callable[[int], (int, str)] # result must be a type
For typing.List[int, str] the whole __getitem__ body runs twice, including the per-argument _type_check calls, before the TypeError is raised.
The relevant code is in Lib/typing.py:
def inner(*args, **kwds):
try:
return _caches[func](*args, **kwds)
except TypeError:
pass # All real errors (not unhashable args) are raised below.
return func(*args, **kwds)
The fix is to only fall back when the arguments are actually unhashable, and to re-raise otherwise so the function is not called a second time.
Reproduced on current main.
Linked PRs
Bug report
While looking at the typing module I noticed that
_tp_cachecan run the function it wraps twice._tp_cachewraps__getitem__and similar methods with anlru_cache. Some of the arguments passed to those methods can be unhashable, so it calls the cached version inside atryand falls back to calling the original function when aTypeErrorshows up, on the assumption that theTypeErrorcame from trying to hash an unhashable argument.The problem is that the wrapped function can also raise
TypeErroron its own, for reasons that have nothing to do with hashing. When that happens with hashable arguments, the function runs once inside the cached call and raises, theexcept TypeErrorcatches it, and then the fallback runs the same function a second time before the error finally propagates.A small reproduction:
It is also reachable through the public API. Any subscription whose cached
__getitem__validates its arguments and raisesTypeErrorfor hashable arguments hits this:For
typing.List[int, str]the whole__getitem__body runs twice, including the per-argument_type_checkcalls, before theTypeErroris raised.The relevant code is in
Lib/typing.py:The fix is to only fall back when the arguments are actually unhashable, and to re-raise otherwise so the function is not called a second time.
Reproduced on current
main.Linked PRs