I am trying to use one of the new capabilities of python 3.8 (currently using 3.8.3). Following the documentation I tried the example provided in the docs:
from functools import singledispatchmethod
class Negator:
@singledispatchmethod
@classmethod
def neg(cls, arg):
raise NotImplementedError("Cannot negate a")
@neg.register
@classmethod
def _(cls, arg: int):
return -arg
@neg.register
@classmethod
def _(cls, arg: bool):
return not arg
Negator.neg(1)
This, however, yields the following error:
...
TypeError: Invalid first argument to `register()`: <classmethod object at 0x7fb9d31b2460>. Use either `@register(some_class)` or plain `@register` on an annotated function.
How can I create a generic class method? Is there something I am missing in my example?
Update:
I have read Aashish A's answer and it seems lik an on-going issue. I have managed to solve my problem the following way.
from functools import singledispatchmethod
class Negator:
@singledispatchmethod
@staticmethod
def neg(arg):
raise NotImplementedError("Cannot negate a")
@neg.register
def _(arg: int):
return -arg
@neg.register
def _(arg: bool):
return not arg
print(Negator.neg(False))
print(Negator.neg(-1))
This seems to work in version 3.8.1 and 3.8.3, however it seems it shouldn't as I am not using the staticmethod decorator on neither the undescore functions. This DOES work with classmethods, even tho the issue seems to indicate the opposite.
Keep in mind if you are using an IDE that the linter won't be happy with this approach, throwing a lot of errors.
This seems to be a bug in the functools library documented in this issue.
A workaround, while the bugfix is not merged, is to patch singledispatchmethod.register()
:
from functools import singledispatchmethod
def _register(self, cls, method=None):
if hasattr(cls, '__func__'):
setattr(cls, '__annotations__', cls.__func__.__annotations__)
return self.dispatcher.register(cls, func=method)
singledispatchmethod.register = _register
This bug is no longer present in Python >= 3.9.8. In Python 3.9.8, the code for singledispatchmethod
has been tweaked to ensure it works with type annotations and classmethod
s/staticmethod
s. In Python 3.10+, however, the bug was solved as a byproduct of changing the way classmethod
s and staticmethod
s behave with respect to the __annotations__
attribute of the function they're wrapping.
In Python 3.9:
>>> x = lambda y: y
>>> x.__annotations__ = {'y': int}
>>> c = classmethod(x)
>>> c.__annotations__
Traceback (most recent call last):
File "<pyshell#37>", line 1, in <module>
c.__annotations__
AttributeError: 'classmethod' object has no attribute '__annotations__'
In Python 3.10+:
>>> x = lambda y: y
>>> x.__annotations__ = {'y': int}
>>> c = classmethod(x)
>>> c.__annotations__
{'y': <class 'int'>}
This change appears to have solved the issue with singledispatchmethod
, meaning that in Python 3.9.8 and Python 3.10+, the following code works fine:
from functools import singledispatchmethod
class Negator:
@singledispatchmethod
@classmethod
def neg(cls, arg):
raise NotImplementedError(f"Cannot negate object of type '{type(arg).__name__}'")
@neg.register
@classmethod
def _(cls, arg: int) -> int:
return -arg
@neg.register
@classmethod
def _(cls, arg: bool) -> bool:
return not arg
print(Negator.neg(1))
print(Negator.neg(False))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With