This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: functools.singledispatch doesn't verify annotation is on FIRST parameter
Type: behavior Stage: patch review
Components: Library (Lib) Versions: Python 3.11, Python 3.10, Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: AlexWaygood, Dutcho, FFY00, bim_bam, lukasz.langa, rhettinger
Priority: normal Keywords: patch

Created on 2020-05-01 07:04 by Dutcho, last changed 2022-04-11 14:59 by admin.

Pull Requests
URL Status Linked Edit
PR 19871 open FFY00, 2020-05-02 23:44
Messages (4)
msg367824 - (view) Author: (Dutcho) Date: 2020-05-01 07:04
From Python 3.7, `functools.singledispatch` makes the `register()` attribute of the generic function infer the type of the first argument automatically for functions annotated with types. That's great for DRY.
However, in 3.7 and 3.8, no check is made that the *first* parameter of the registered function is actually annotated; *any* annotation suffices, even the *return* one.

Example:
    ```
    >>> @functools.singledispatch
    ... def func(arg):...
    >>> @func.register
    ... def _int(arg) -> int:...
    >>> @func.register
    ... def _str(arg) -> str:...
```
No errors happen, although the return type, *not* `arg`, is annotated.
This results in:
    ```
    >>> func.registry
    mappingproxy({<class 'object'>: <function func>, <class 'int'>: <function _int>, <class 'str'>: <function _str>})

    ```
Obviously, that doesn't dispatch correctly.

Note that un-annotated functions *are* caught:
    ```
    >>> @func.register
    ... def _no_annotation(arg): ...
    Traceback (most recent call last):
    ...
    TypeError: Invalid first argument to `register()`: <function _no_annotation at 0x000001D769A43D30>. Use either `@register(some_class)` or plain `@register` on an annotated function.
    ```
msg368079 - (view) Author: (Dutcho) Date: 2020-05-04 20:00
I'm afraid my "even return" was interpreted in https://github.com/python/cpython/pull/19871 as "only return", while as stated "any annotation" suffices. To rephrase:
If the *first* parameter of the registered function isn't annotated, any non-first annotation suffices for registering, but will not dispatch correctly.
Example:
    ```
    >>> @functools.singledispatch
    ... def func(arg, x):...
    >>> @func.register
    ... def _int(arg, x:int):...
    >>> @func.register
    ... def _str(arg, x:str):...
```
No errors happen, although parameter `x` is annotated, not the first parameter `arg`. So `func()` will dispatch on the type of `arg` according to the annotation of `x`.
So I'm afraid the PR solves the specific "return" example case, but not the flagged general issue.
msg368095 - (view) Author: Filipe Laíns (FFY00) * (Python triager) Date: 2020-05-05 00:04
Right, forgot about that. We can get the first argument name from inspect.signature and then fetch it from the get_type_hints dictionary, I don't know a better way to do it.
msg406062 - (view) Author: Alex Waygood (AlexWaygood) * (Python triager) Date: 2021-11-09 22:47
Reproduced on 3.11.
History
Date User Action Args
2022-04-11 14:59:30adminsetgithub: 84644
2021-11-09 22:47:09AlexWaygoodsetnosy: + rhettinger, lukasz.langa, AlexWaygood

messages: + msg406062
versions: + Python 3.9, Python 3.10, Python 3.11, - Python 3.7
2020-05-05 00:04:18FFY00setmessages: + msg368095
2020-05-04 20:00:54Dutchosetmessages: + msg368079
2020-05-02 23:44:11FFY00setkeywords: + patch
nosy: + FFY00

pull_requests: + pull_request19184
stage: patch review
2020-05-01 16:26:21bim_bamsetnosy: + bim_bam
2020-05-01 07:04:01Dutchocreate