-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
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
Make sure that typing cache differentiate Union[int, str]
and Union[str, int]
#103749
Comments
Thanks -- go for it! (Are we also considering adding a cache for |
I did a quick experiment. I’m sure you can come up with something better, but maybe it’s a good starting point: https://github.com/python/cpython/compare/main...adriangb:cpython:union-cache?expand=1 |
@gvanrossum, yes. I am working on it in #103541 (comment) @adriangb yes, this is the same idea I had: tweaking the |
I had some time to work on this today. I have a partially working solution, but I am not a big fan of what I came up with. So, here's how my new def _tp_cache(func=None, /, *, typed=False):
"""Internal wrapper caching __getitem__ of generic types with a fallback to
original function for non-hashable arguments.
"""
def decorator(func):
# The callback 'inner' references the newly created lru_cache
# indirectly by performing a lookup in the global '_caches' dictionary.
# This breaks a reference that can be problematic when combined with
# C API extensions that leak references to types. See GH-98253.
def _tp_wrapper(func):
def ignored_first_arg(tp_key, /, *args, **kwargs):
# We just ignore the first `tp_key`, since it is a special
# hack to distingush unions from each other.
return func(*args, **kwargs)
return ignored_first_arg
def _tp_cache_key(args):
new_args = []
for arg in args:
if isinstance(arg, _UnionGenericAlias):
new_args.append((Union, *arg.__args__))
else:
new_args.append(arg)
return tuple(new_args)
cache = functools.lru_cache(typed=typed)(_tp_wrapper(func))
_caches[func] = cache
_cleanups.append(cache.cache_clear)
del cache
@functools.wraps(func)
def inner(*args, **kwds):
try:
return _caches[func](_tp_cache_key(args), *args, **kwds)
except TypeError:
pass # All real errors (not unhashable args) are raised below.
return func(*args, **kwds)
return inner
if func is not None:
return decorator(func)
return decorator It solves the problem partially. All tests pass. Plus, I have this extra test that also passes: @cpython_only
def test_special_caching(self):
types = [
Union[int, str],
Optional[int],
List[Union[int, str]],
List[List[Union[int, str]]],
]
for typ in types:
with self.subTest(typ=typ):
self.assertIs(typ, typ)
self.assertIsNot(Union[str, int], Union[int, str])
self.assertIsNot(Union[str, None], Union[None, str])
self.assertIsNot(Union[type(None), int], Optional[int])
self.assertIs(Union[int, type(None)], Optional[int])
self.assertIsNot(List[Union[str, int]],
List[Union[int, str]]) But, there's still a problem with deeply nested What can we do?
What do others think? |
What happens if you remove |
It won't change much, because it will translate |
Is there custom Python logic that would work? How bad would the performance impact be? As far as I know the purpose of this cache is memory, not performance. If the difference of doing it in Python is 10% slower but the cache itself is 200% slower then maybe it’s okay because we’ve already accepted the performance trade off of having a cache? |
@sobolevn what if instead of using Edit: I tried it in the branch I mentioned above (https://github.com/python/cpython/compare/main...adriangb:cpython:union-cache?expand=1) and it seems to work, albeit with a probably less performant and elegant version of |
Using |
Previous discussions:
types.GenericAlias
objects #103541 (comment)It was decided (@gvanrossum and @samuelcolvin) that there's not need to change
__hash__
or__eq__
ofUnion
objects, we can just change how the typing cache works.In other words, we need to change this behavior:
To be:
This way we can fix this confusing behavior:
Instead it would be:
I am interested in working on this:
typing.py
is my favourite module + I have a highly related opened PR withtypes.GenericAlias
cache.If someone else is already working on it, please let me know :)
The text was updated successfully, but these errors were encountered: