Skip to content

Commit 641ef0a

Browse files
johnslavikJelleZijlstrajaracopR0Pspicnixz
authored
gh-84644: Deprecate singledispatch on return annotation (GH-143465)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> Co-authored-by: Carey Metcalfe <carey@cmetcalfe.ca> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 0d35e3b commit 641ef0a

6 files changed

Lines changed: 103 additions & 18 deletions

File tree

‎Doc/deprecations/pending-removal-in-3.21.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ Pending removal in Python 3.21
1818
* The ``dims`` property of ``ast.Tuple`` will be removed in Python 3.21. Use
1919
the ``ast.Tuple.elts`` property instead.
2020

21+
* :mod:`functools`:
22+
23+
* Using a return annotation to infer the dispatch type for
24+
:func:`functools.singledispatch` or
25+
:class:`functools.singledispatchmethod` is deprecated since Python 3.16
26+
and will raise :exc:`TypeError` in Python 3.21. Annotate the dispatch
27+
parameter or pass the dispatch type explicitly instead.
28+
(Contributed by Bartosz Sławecki in :gh:`143465`.)
29+
2130
* :mod:`struct`:
2231

2332
* Soft-deprecated since Python 3.15, using ``'F'`` and ``'D'`` type codes are now

‎Doc/library/functools.rst‎

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -506,24 +506,37 @@ The :mod:`!functools` module defines the following functions:
506506
:no-typesetting:
507507

508508
To add overloaded implementations to the function, use the :func:`!register`
509-
attribute of the generic function, which can be used as a decorator. For
510-
functions annotated with types, the decorator will infer the type of the
511-
first argument automatically::
509+
attribute of the generic function, which can be used as a decorator.
510+
Pass the dispatch type to the decorator::
512511

513-
>>> @fun.register
514-
... def _(arg: int, verbose=False):
512+
>>> @fun.register(int)
513+
... def _(arg, verbose=False):
515514
... if verbose:
516515
... print("Strength in numbers, eh?", end=" ")
517516
... print(arg)
518517
...
519-
>>> @fun.register
520-
... def _(arg: list, verbose=False):
518+
>>> @fun.register(list)
519+
... def _(arg, verbose=False):
521520
... if verbose:
522521
... print("Enumerate this:")
523522
... for i, elem in enumerate(arg):
524523
... print(i, elem)
525524

526-
:class:`typing.Union` can also be used::
525+
As a shortcut, for functions annotated with types, the decorator can infer
526+
the dispatch type without an explicit type argument::
527+
528+
>>> @fun.register
529+
... def _(arg: int, verbose=False):
530+
... if verbose:
531+
... print("Strength in numbers, eh?", end=" ")
532+
... print(arg)
533+
...
534+
535+
This shortcut uses the first annotation, even if it belongs to another
536+
parameter or the return value. Annotate the dispatch parameter, or pass the
537+
dispatch type explicitly if its annotation is not first.
538+
539+
:class:`typing.Union` can also be used with this shortcut::
527540

528541
>>> @fun.register
529542
... def _(arg: int | float, verbose=False):
@@ -540,16 +553,6 @@ The :mod:`!functools` module defines the following functions:
540553
... print(i, elem)
541554
...
542555

543-
For code which doesn't use type annotations, the appropriate type
544-
argument can be passed explicitly to the decorator itself::
545-
546-
>>> @fun.register(complex)
547-
... def _(arg, verbose=False):
548-
... if verbose:
549-
... print("Better than complicated.", end=" ")
550-
... print(arg.real, arg.imag)
551-
...
552-
553556
For code that dispatches on a collections type (e.g., ``list``), but wants
554557
to typehint the items of the collection (e.g., ``list[int]``), the
555558
dispatch type should be passed explicitly to the decorator itself with the
@@ -662,6 +665,12 @@ The :mod:`!functools` module defines the following functions:
662665
The :func:`~singledispatch.register` attribute now supports
663666
:class:`typing.Union` as a type annotation.
664667

668+
.. deprecated:: 3.16
669+
Using a return annotation to infer the dispatch type is deprecated and
670+
will raise :exc:`TypeError` in Python 3.21. Annotate the dispatch
671+
parameter or pass the dispatch type explicitly to :func:`!register`
672+
instead.
673+
665674

666675
.. class:: singledispatchmethod(func)
667676

@@ -686,6 +695,20 @@ The :mod:`!functools` module defines the following functions:
686695
def _(self, arg: bool):
687696
return not arg
688697

698+
When *self* or *cls* is annotated, pass the dispatch type explicitly rather
699+
than relying on annotation inference::
700+
701+
from typing import Self
702+
703+
class Negator:
704+
@singledispatchmethod
705+
def neg(self, arg):
706+
raise NotImplementedError("Cannot negate a")
707+
708+
@neg.register(int)
709+
def _(self: Self, arg: int):
710+
return -arg
711+
689712
``@singledispatchmethod`` supports nesting with other decorators such as
690713
:deco:`classmethod`. Note that to allow for
691714
``dispatcher.register``, ``singledispatchmethod`` must be the *outer most*

‎Doc/whatsnew/3.16.rst‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,16 @@ New deprecations
10791079
3.9, now issues a deprecation warning on use. This property is slated for
10801080
removal in 3.21. Use ``ast.Tuple.elts`` instead.
10811081

1082+
* :mod:`functools`:
1083+
1084+
* Using a return annotation to infer the dispatch type for
1085+
:func:`functools.singledispatch` or
1086+
:class:`functools.singledispatchmethod` now emits a
1087+
:exc:`DeprecationWarning` and will raise :exc:`TypeError` in Python 3.21.
1088+
Annotate the dispatch parameter or pass the dispatch type explicitly
1089+
instead.
1090+
(Contributed by Bartosz Sławecki in :gh:`143465`.)
1091+
10821092
* :mod:`struct`:
10831093

10841094
* The ``'F'`` and ``'D'`` type codes, soft-deprecated since Python 3.15,

‎Lib/functools.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,16 @@ def register(cls, func=None):
990990
f"Invalid annotation for {argname!r}. "
991991
f"{cls!r} is not a class."
992992
)
993+
if argname == 'return':
994+
import warnings
995+
warnings.warn(
996+
"Using the return annotation to infer the dispatch type "
997+
"is deprecated and will raise TypeError in Python 3.21. "
998+
"Annotate the dispatch parameter or pass the dispatch "
999+
"type explicitly to register() decorator.",
1000+
DeprecationWarning,
1001+
skip_file_prefixes=(__file__,),
1002+
)
9931003

9941004
if isinstance(cls, UnionType):
9951005
for arg in cls.__args__:

‎Lib/test/test_functools.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3371,6 +3371,34 @@ def _(arg: typing.Union[int, typing.Iterable[str]]):
33713371
'int | typing.Iterable[str] not all arguments are classes.'
33723372
)
33733373

3374+
def test_return_annotation_deprecated(self):
3375+
msg = (
3376+
"Using the return annotation to infer the dispatch type "
3377+
r"is deprecated and will raise TypeError in Python 3\.21\."
3378+
)
3379+
3380+
@functools.singledispatch
3381+
def func(arg):
3382+
return "base"
3383+
def impl(arg) -> int:
3384+
return "int"
3385+
with self.assertWarnsRegex(DeprecationWarning, msg) as cm:
3386+
func.register(impl)
3387+
self.assertEqual(cm.filename, __file__)
3388+
self.assertIs(func.registry[int], impl)
3389+
3390+
class C:
3391+
@functools.singledispatchmethod
3392+
def method(self, arg):
3393+
return "base"
3394+
def method_impl(self) -> str:
3395+
return "str"
3396+
with self.assertWarnsRegex(DeprecationWarning, msg) as cm:
3397+
C.method.register(method_impl)
3398+
self.assertEqual(cm.filename, __file__)
3399+
dispatcher = C.__dict__['method'].dispatcher
3400+
self.assertIs(dispatcher.registry[str], method_impl)
3401+
33743402
def test_invalid_positional_argument(self):
33753403
@functools.singledispatch
33763404
def f(*args, **kwargs):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Deprecate using a return annotation to infer the dispatch type for
2+
:func:`functools.singledispatch` and
3+
:class:`functools.singledispatchmethod`. It will raise :exc:`TypeError` in
4+
Python 3.21. Annotate the dispatch parameter or pass the dispatch type
5+
explicitly instead. Patch by Bartosz Sławecki.

0 commit comments

Comments
 (0)