[Python-checkins] [3.9] bpo-39679: Fix `singledispatchmethod` `classmethod`/`staticmethod` bug (GH-29087)

ambv webhook-mailer at python.org
Thu Oct 28 12:02:12 EDT 2021


https://github.com/python/cpython/commit/97388c204b557f30e48a2b2ef826868702204cf2
commit: 97388c204b557f30e48a2b2ef826868702204cf2
branch: 3.9
author: Alex Waygood <Alex.Waygood at Gmail.com>
committer: ambv <lukasz at langa.pl>
date: 2021-10-28T18:02:04+02:00
summary:

[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 at gmail.com>
Co-authored-by: Łukasz Langa <lukasz at langa.pl>

files:
A Misc/NEWS.d/next/Library/2021-10-20-10-07-44.bpo-39679.nVYJJ3.rst
M Lib/functools.py
M Lib/test/test_functools.py

diff --git a/Lib/functools.py b/Lib/functools.py
index 97744a869563d..5054e281ad281 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -906,6 +906,12 @@ def register(self, cls, method=None):
 
         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 987020ea007fa..96e93ed8eab34 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -2427,6 +2427,48 @@ def _(self, arg: str):
         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 0000000000000..b0656aac51677
--- /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.



More information about the Python-checkins mailing list