Repository navigation
functools' singledispatch does not support GenericAlias #90190
Description
Activity
functools' singledispatch does not support GenericAlias
from functools import singledispatch @singledispatch def func(x): print("any") @func.register def _(x: list[str]): print("list[str]") func(["a", "b"])
- addedtype-featureA feature request or enhancementA feature request or enhancement3.11only security fixesonly security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Dec 10, 2021 My opinion is that supporting
GenericAliashere would be a bad idea. Associating an implementation of the function with the argument typelist[str]is ambiguous. Would this implementation be called if any argument of typelistwas supplied, or would it only be called if all elements in the list were of typestr?The first option would be efficient, simple, and similar to the way singledispatch treats most other argument-types. However, it would be unintuitive.
The second option would be more intuitive, but could be extremely inefficient if a very long list was passed in. It would also make the code more complicated.
It would be well worth it to improve the error message, however:
>>> from functools import singledispatch >>> @singledispatch ... def func(arg): ... raise NotImplementedError ... >>> @func.register ... def _(arg: list[str]): ... print('Got a list of strings') ... >>> func(1) Traceback (most recent call last): File "/usr/local/lib/python3.9/functools.py", line 830, in dispatch impl = dispatch_cache[cls] File "/usr/local/lib/python3.9/weakref.py", line 405, in __getitem__ return self.data[ref(key)] KeyError: <weakref at 0x7f2a0d9141d0; to 'type' at 0x7f2a0e08b200 (int)> During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/local/lib/python3.9/functools.py", line 833, in dispatch impl = registry[cls] KeyError: <class 'int'> During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.9/functools.py", line 877, in wrapper return dispatch(args[0].__class__)(*args, **kw) return dispatch(args[0].__class__)(*args, **kw) File "/usr/local/lib/python3.9/functools.py", line 835, in dispatch impl = _find_impl(cls, registry) File "/usr/local/lib/python3.9/functools.py", line 782, in _find_impl mro = _compose_mro(cls, registry.keys()) File "/usr/local/lib/python3.9/functools.py", line 743, in _compose_mro types = [n for n in types if is_related(n)] File "/usr/local/lib/python3.9/functools.py", line 743, in <listcomp> types = [n for n in types if is_related(n)] File "/usr/local/lib/python3.9/functools.py", line 742, in is_related and issubclass(cls, typ)) TypeError: issubclass() argument 2 cannot be a parameterized genericThe above traceback is because the
isinstance(list[str], type)check at Lib/functools.py:848 evaluates toTrue. Related: bpo-45665.Yes, it is related to bpo-45665. It is a complicated case due to coincidence of several circumstances.
-
isinstance(list[int], type) is True, while isinstance(typing.List[int], type) is False. list[int] is considered a type in this check.
-
list[int].__mro__ == list.__mro__, while typing.List[int] does not have the __mro__ attribute. list[int] is considered a type in this check.
-
issubclass(cls, list[int]) raises a TypeError (the same for typing.List[int]). list[int] cannot be used as a type here.
-
2-argument registry() does not check the type of its first argument. f.registry(42, ...) is silently passed.
In 2-argument registry() typing.List[int] is passed due to (4) and ignored in dispatch() due to (2). list[int] is passed due to (4), but caused error due to (3).
In other uses of registry() (1-argument decorator factory and decorator with annotations) typing.List[int] is not passed due to 1. list[int] is passed due to (1) and caused error due to (3).
The proposed PR makes list[int] be treated the same way as typing.List[int]. It also makes 2-argument registry() rejecting invalid first argument, so all three forms of registry() accept and reject now the same types.
-
8 remaining items
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Dec 11, 2021 It would be well worth it to improve the error message, however:
>>> from functools import singledispatch >>> @singledispatch ... def func(arg): ... raise NotImplementedError ... >>> @func.register ... def _(arg: list[str]): ... print('Got a list of strings') ... >>> func(1) Traceback (most recent call last): File "/usr/local/lib/python3.9/functools.py", line 830, in dispatch impl = dispatch_cache[cls] File "/usr/local/lib/python3.9/weakref.py", line 405, in __getitem__ return self.data[ref(key)] KeyError: <weakref at 0x7f2a0d9141d0; to 'type' at 0x7f2a0e08b200 (int)> During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/local/lib/python3.9/functools.py", line 833, in dispatch impl = registry[cls] KeyError: <class 'int'> During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.9/functools.py", line 877, in wrapper return dispatch(args[0].__class__)(*args, **kw) return dispatch(args[0].__class__)(*args, **kw) File "/usr/local/lib/python3.9/functools.py", line 835, in dispatch impl = _find_impl(cls, registry) File "/usr/local/lib/python3.9/functools.py", line 782, in _find_impl mro = _compose_mro(cls, registry.keys()) File "/usr/local/lib/python3.9/functools.py", line 743, in _compose_mro types = [n for n in types if is_related(n)] File "/usr/local/lib/python3.9/functools.py", line 743, in <listcomp> types = [n for n in types if is_related(n)] File "/usr/local/lib/python3.9/functools.py", line 742, in is_related and issubclass(cls, typ)) TypeError: issubclass() argument 2 cannot be a parameterized genericI came across this today, and I was quite surprised I could not do this but then it made sense since we cannot do
isinstanceonGenericAlias. It certainly makes the idea of usingfunctools.singledispatchless appealing. If I had 3.10 available to me I would just use match case.I see there is still no documentation stating this is not supported. https://docs.python.org/3/library/functools.html#functools.singledispatch
Proposal, I'll create a PR adding an example with additional example of how to properly type hint it correctly? I'm not sure if there even is a way to properly type hint that a function takes a
list[str]while still dispatching using that type hint and not justlisti.e.
@singledispatch def func(x): print("any") @func.register def _(x: list[str]): print("list[str]")raises error
TypeError: Invalid annotation for 'a'. list[str] is not a class.but I'm not sure of a way to actually type hint thatfuncshould take alist[str]and not just alist.If you want to use type hints with generics in a singledispatch function, I'd recommend doing something like this:
@singledispatch def func(x): print("any") @func.register(list) def _(x: list[str]): print("list[str]")
(But note that that will dispatch on any call that passes a list, not just calls that pass lists where all items in the list are
strs.)A PR to improve the docs would be welcome. If you ping me on the PR, I can review it.
- added a commit that references this issue
on Sep 27, 2024 - added a commit that references this issue
on Sep 27, 2024 - added a commit that references this issue
on Sep 27, 2024
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs
singledispatchwith precise collection type hints #116544singledispatchwith precise collection type hints (GH-116544) #124710singledispatchwith precise collection type hints (GH-116544) #124711