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: Conflict between using annotations in singledispatch() and MyPy
Type: Stage: resolved
Components: Library (Lib) Versions: Python 3.11, Python 3.10, Python 3.9
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: AlexWaygood, gvanrossum, kj, lukasz.langa, rhettinger, serhiy.storchaka
Priority: normal Keywords:

Created on 2021-12-29 08:28 by serhiy.storchaka, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (7)
msg409295 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2021-12-29 08:28
You can dispatch on collections.abc.Sequence.

@functools.singledispatch
def f(a: collections.abc.Sequence) -> None:
    pass

But MyPy complains about this:

error: Missing type parameters for generic type "Sequence"

MyPy requires parametrized generic (like collections.abc.Sequence[int]), but singledispatch() does not work with it.
msg409310 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-12-29 17:27
That mypy error is only reported with a certain mypy setting (--strict, or probably one of the specific settings that that turns on).

We won't be supporting Sequence[Any] at runtime (because we only support actual types), but we should support Sequence, since that *is* an actual type (at least collections.abc.Sequence is).

So IMO this is either a problem in mypy (though in that case probably in typeshed) or a situation for which the user should just silence mypy (it's not perfect and never claimed to be).

But I think there's nothing for us to do in Python itself, so this bug should be closed.
msg409313 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2021-12-29 18:06
I do not think there is a problem in MyPy. What if use __origin__ for dispatching? Registering with parametrized generics with the same __origin__ will be error.

@sigledispatch
def f(a: int) -> None:
    pass

@f.register  # ok
def _(a: list[int]) -> None:
    pass

@f.register  # runtime error
def _(a: list[str]) -> None:
    pass

@f.register  # runtime error
def _(a: list) -> None:
    pass

f(1)  # ok
f([1])  # ok
f([])  # ok
f(['abc'])  # static type checking error

I think that it will have advantage of stronger static type checking.
msg409314 - (view) Author: Alex Waygood (AlexWaygood) * (Python triager) Date: 2021-12-29 18:15
I like Serhiy's idea from a type-checking perspective. It would mean we could continue to have sophisticated type inference from mypy/pyright/etc. inside singledispatch function definitions. It would also mean people could use type annotations in singledispatch functions in much the same way as in the rest of their code, instead of having to remember that *this specific stdlib function* works differently. Lastly, it avoids having to have each type-checker separately special-case singledispatch so that they do not raise an error when an unparameterised generic is used as a type annotation. 

My concern, however, is that the runtime semantics are slightly unintuitive. Registering an implementation to the "type" `list[str]` (and having it succeed without an error being raised) might give the false implication that there will be runtime checking of whether all the elements in a list are strings.

I also think that GenericAlias objects should probably only be accepted in the form of `singledispatch.register()` that parses type annotations. There's no use case for allowing GenericAlias objects in the other forms of registering implementations.
msg409336 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-12-29 23:58
If we allow registering list[int] but give it the same meaning as registering plain list (at runtime), that would violate user expectations pretty strongly -- for the same reason why we don't allow isinstance(x, list[int]).

If you want stronger checking inside the function you should probably do something like

@foo.register
def _(_a: list) -> ...:
    a: list[int] = _a
    ...

That said I don't care enough about singledispatch to argue strongly.
msg409339 - (view) Author: Alex Waygood (AlexWaygood) * (Python triager) Date: 2021-12-30 00:23
Yeah, I think I agree with Guido. Mypy only has partial support for singledispatch anyway, and only achieves that through a plugin, so special-casing by type-checkers is inevitable.
msg409351 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2021-12-30 07:36
Okay. As a workaround we can explicitly specify the dispatching type:

@f.register(list)
def _(a: list[int]) -> None:
    pass
History
Date User Action Args
2022-04-11 14:59:53adminsetgithub: 90349
2021-12-30 07:36:01serhiy.storchakasetstatus: open -> closed
resolution: rejected
messages: + msg409351

stage: resolved
2021-12-30 00:23:49AlexWaygoodsetmessages: + msg409339
2021-12-29 23:58:53gvanrossumsetmessages: + msg409336
2021-12-29 18:15:43AlexWaygoodsetmessages: + msg409314
2021-12-29 18:06:31serhiy.storchakasetmessages: + msg409313
2021-12-29 17:27:30gvanrossumsetmessages: + msg409310
2021-12-29 10:28:38AlexWaygoodsetnosy: + lukasz.langa, AlexWaygood
2021-12-29 08:28:12serhiy.storchakacreate