diff options
author | Alex Waygood <Alex.Waygood@Gmail.com> | 2021-10-28 17:02:04 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-28 18:02:04 +0200 |
commit | 97388c204b557f30e48a2b2ef826868702204cf2 (patch) | |
tree | c6e20a9bb0e3da86ebd1777350a1694afafa3779 | |
parent | 8365a5b5abe51cbe4151d89a5d0a993273320067 (diff) | |
download | cpython-git-97388c204b557f30e48a2b2ef826868702204cf2.tar.gz |
[3.9] bpo-39679: Fix `singledispatchmethod` `classmethod`/`staticmethod` bug (GH-29087)
This commit fixes a bug in the 3.9 branch where stacking
`@functools.singledispatchmethod` on top of `@classmethod` or `@staticmethod`
caused an exception to be raised if the method was registered using
type-annotations rather than `@method.register(int)`. Tests for this scenario
were added to the 3.11 and 3.10 branches in #29034 and #29072; this commit
also backports those tests to the 3.9 branch.
Co-authored-by: Yurii Karabas <1998uriyyo@gmail.com>
Co-authored-by: Ćukasz Langa <lukasz@langa.pl>
-rw-r--r-- | Lib/functools.py | 6 | ||||
-rw-r--r-- | Lib/test/test_functools.py | 42 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst | 3 |
3 files changed, 51 insertions, 0 deletions
diff --git a/Lib/functools.py b/Lib/functools.py index 97744a8695..5054e281ad 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -906,6 +906,12 @@ class singledispatchmethod: Registers a new implementation for the given *cls* on a *generic_method*. """ + # bpo-39679: in Python <= 3.9, classmethods and staticmethods don't + # inherit __annotations__ of the wrapped function (fixed in 3.10+ as + # a side-effect of bpo-43682) but we need that for annotation-derived + # singledispatches. So we add that just-in-time here. + if isinstance(cls, (staticmethod, classmethod)): + cls.__annotations__ = getattr(cls.__func__, '__annotations__', {}) return self.dispatcher.register(cls, func=method) def __get__(self, obj, cls=None): diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 987020ea00..96e93ed8ea 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2427,6 +2427,48 @@ class TestSingleDispatch(unittest.TestCase): self.assertEqual(a.t(''), "str") self.assertEqual(a.t(0.0), "base") + def test_staticmethod_type_ann_register(self): + class A: + @functools.singledispatchmethod + @staticmethod + def t(arg): + return arg + @t.register + @staticmethod + def _(arg: int): + return isinstance(arg, int) + @t.register + @staticmethod + def _(arg: str): + return isinstance(arg, str) + a = A() + + self.assertTrue(A.t(0)) + self.assertTrue(A.t('')) + self.assertEqual(A.t(0.0), 0.0) + + def test_classmethod_type_ann_register(self): + class A: + def __init__(self, arg): + self.arg = arg + + @functools.singledispatchmethod + @classmethod + def t(cls, arg): + return cls("base") + @t.register + @classmethod + def _(cls, arg: int): + return cls("int") + @t.register + @classmethod + def _(cls, arg: str): + return cls("str") + + self.assertEqual(A.t(0).arg, "int") + self.assertEqual(A.t('').arg, "str") + self.assertEqual(A.t(0.0).arg, "base") + def test_invalid_registrations(self): msg_prefix = "Invalid first argument to `register()`: " msg_suffix = ( diff --git a/Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst b/Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst new file mode 100644 index 0000000000..b0656aac51 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst @@ -0,0 +1,3 @@ +Fix bug in :class:`functools.singledispatchmethod` that caused it to fail +when attempting to register a :func:`classmethod` or :func:`staticmethod` +using type annotations. Patch contributed by Alex Waygood. |