Skip to content

Commit d466dc4

Browse files
committed
gh-130827: Support typing.Self in singledispatchmethod()
1 parent 3929af5 commit d466dc4

File tree

2 files changed

+48
-12
lines changed

2 files changed

+48
-12
lines changed

Lib/functools.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -885,15 +885,7 @@ def _find_impl(cls, registry):
885885
match = t
886886
return registry.get(match)
887887

888-
def singledispatch(func):
889-
"""Single-dispatch generic function decorator.
890-
891-
Transforms a function into a generic function, which can have different
892-
behaviours depending upon the type of its first argument. The decorated
893-
function acts as the default implementation, and additional
894-
implementations can be registered using the register() attribute of the
895-
generic function.
896-
"""
888+
def _singledispatchimpl(func, is_method):
897889
# There are many programs that use functools without singledispatch, so we
898890
# trade-off making singledispatch marginally slower for the benefit of
899891
# making start-up of such applications slightly faster.
@@ -963,9 +955,19 @@ def register(cls, func=None):
963955
func = cls
964956

965957
# only import typing if annotation parsing is necessary
966-
from typing import get_type_hints
958+
from typing import get_type_hints, Self
967959
from annotationlib import Format, ForwardRef
968-
argname, cls = next(iter(get_type_hints(func, format=Format.FORWARDREF).items()))
960+
hints_iter = iter(get_type_hints(func, format=Format.FORWARDREF).items())
961+
argname, cls = next(hints_iter)
962+
if cls is Self:
963+
if not is_method:
964+
raise TypeError(
965+
f"Invalid annotation for {argname!r}. ",
966+
"typing.Self can only be used with singledispatchmethod()"
967+
)
968+
else:
969+
argname, cls = next(hints_iter)
970+
969971
if not _is_valid_dispatch_type(cls):
970972
if _is_union_type(cls):
971973
raise TypeError(
@@ -1010,6 +1012,16 @@ def wrapper(*args, **kw):
10101012
update_wrapper(wrapper, func)
10111013
return wrapper
10121014

1015+
def singledispatch(func):
1016+
"""Single-dispatch generic function decorator.
1017+
1018+
Transforms a function into a generic function, which can have different
1019+
behaviours depending upon the type of its first argument. The decorated
1020+
function acts as the default implementation, and additional
1021+
implementations can be registered using the register() attribute of the
1022+
generic function.
1023+
"""
1024+
return _singledispatchimpl(func, is_method=False)
10131025

10141026
# Descriptor version
10151027
class singledispatchmethod:
@@ -1023,7 +1035,7 @@ def __init__(self, func):
10231035
if not callable(func) and not hasattr(func, "__get__"):
10241036
raise TypeError(f"{func!r} is not callable or a descriptor")
10251037

1026-
self.dispatcher = singledispatch(func)
1038+
self.dispatcher = _singledispatchimpl(func, is_method=True)
10271039
self.func = func
10281040

10291041
def register(self, cls, method=None):

Lib/test/test_functools.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3362,6 +3362,30 @@ def _(item, arg: bytes) -> str:
33623362
self.assertEqual(str(Signature.from_callable(A.static_func)),
33633363
'(item, arg: int) -> str')
33643364

3365+
def test_typing_self(self):
3366+
# gh-130827: typing.Self with singledispatchmethod() didn't work
3367+
class Foo:
3368+
@functools.singledispatchmethod
3369+
def bar(self: typing.Self, arg: int | str) -> int | str: ...
3370+
3371+
@bar.register
3372+
def _(self: typing.Self, arg: int) -> int:
3373+
return arg
3374+
3375+
3376+
foo = Foo()
3377+
self.assertEqual(foo.bar(42), 42)
3378+
3379+
@functools.singledispatch
3380+
def test(self: typing.Self, arg: int | str) -> int | str:
3381+
pass
3382+
# But, it shouldn't work on singledispatch()
3383+
with self.assertRaises(TypeError):
3384+
@test.register
3385+
def silly(self: typing.Self, arg: int | str) -> int | str:
3386+
pass
3387+
3388+
33653389

33663390
class CachedCostItem:
33673391
_cost = 1

0 commit comments

Comments
 (0)