[issue39679] functools: singledispatchmethod doesn't work with classmethod

Viktor Roytman report at bugs.python.org
Fri Feb 21 16:21:43 EST 2020


Viktor Roytman <viktor.roytman at gmail.com> added the comment:

I tried to apply this change but it didn't work, failing with this error

    $ ~/.pyenv/versions/3.8.1/bin/python -m bad_classmethod_as_documented
    Traceback (most recent call last):
      File "/home/viktor/.pyenv/versions/3.8.1/lib/python3.8/runpy.py", line 193, in _run_module_as_main
        return _run_code(code, main_globals, None,
      File "/home/viktor/.pyenv/versions/3.8.1/lib/python3.8/runpy.py", line 86, in _run_code
        exec(code, run_globals)
      File "/home/viktor/scratch/bad_classmethod_as_documented.py", line 4, in <module>
        class Negator:
      File "/home/viktor/scratch/bad_classmethod_as_documented.py", line 12, in Negator
        def _(cls, arg: int):
      File "/home/viktor/.pyenv/versions/3.8.1/lib/python3.8/functools.py", line 1006, in register
        return self.dispatcher.register(cls, func=method)
      File "/home/viktor/.pyenv/versions/3.8.1/lib/python3.8/functools.py", line 959, in register
        argname, cls = next(iter(get_type_hints(func).items()))
      File "/home/viktor/.pyenv/versions/3.8.1/lib/python3.8/typing.py", line 1252, in get_type_hints
        raise TypeError('{!r} is not a module, class, method, '
    TypeError: <classmethod object at 0x7f84e1e69c40> is not a module, class, method, or function.

After digging around a bit, this diff seems to work (not sure if there's a better way to do it) (also this one doesn't seem to care whether @staticmethod is applied to the implementation methods):

$ diff -u functools-orig.py functools.py
--- functools-orig.py	2020-02-21 16:14:56.141934001 -0500
+++ functools.py	2020-02-21 16:17:19.959905849 -0500
@@ -843,14 +843,18 @@
         if func is None:
             if isinstance(cls, type):
                 return lambda f: register(cls, f)
-            ann = getattr(cls, '__annotations__', {})
+            if isinstance(cls, (classmethod, staticmethod)):
+                ann = getattr(cls.__func__, '__annotations__', {})
+                func = cls.__func__
+            else:
+                ann = getattr(cls, '__annotations__', {})
+                func = cls
             if not ann:
                 raise TypeError(
                     f"Invalid first argument to `register()`: {cls!r}. "
                     f"Use either `@register(some_class)` or plain `@register` "
                     f"on an annotated function."
                 )
-            func = cls
 
             # only import typing if annotation parsing is necessary
             from typing import get_type_hints
@@ -908,6 +912,8 @@
     def __get__(self, obj, cls=None):
         def _method(*args, **kwargs):
             method = self.dispatcher.dispatch(args[0].__class__)
+            if isinstance(self.func, classmethod):
+                return method.__get__(obj, cls)(cls, *args, **kwargs)
             return method.__get__(obj, cls)(*args, **kwargs)
 
         _method.__isabstractmethod__ = self.__isabstractmethod__

----------

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue39679>
_______________________________________


More information about the Python-bugs-list mailing list