Background
Suppose I am to implement a simple decorator @notifyme
that prints a message when the decorated function is invoked. I would like the decorator to accept one argument to print a customized message; the argument (along with the parentheses surrounding the argument) may be omitted, in which case the default message is printed:
@notifyme('Foo is invoked!')
def foo():
pass
@notifyme # instead of @notifyme()
def bar():
pass
To allow the parentheses to be omitted, I have to provide two implementations of @notifyme
:
The first implementation allows the user to customize the message, so it accepts a string as argument and returns a decorator:
def notifyme_customized(message: str) -> Callable[[Callable], Callable]: def decorator(func: Callable) -> Callable: def decorated_func(*args, **kwargs): print(str) return func(*args, **kwargs) return decorated_func return decorator
The second implementation is a decorator itself and uses the first implementation to print a default message:
def notifyme_default(func: Callable) -> Callable: return notifyme_customized('The function is invoked.')(func)
To make the two implementations above use the same name notifyme
, I used functools.singledispatch
to dynamically dispatch the call to notifyme
to one of the two implementations:
# This is a complete minimal reproducible example
from functools import singledispatch
from typing import Callable
@singledispatch
def notifyme(arg):
return NotImplemented
@notifyme.register
def notifyme_customized(message: str) -> Callable[[Callable], Callable]:
def decorator(func: Callable) -> Callable:
def decorated_func(*args, **kwargs):
print(str)
return func(*args, **kwargs)
return decorated_func
return decorator
@notifyme.register
def notifyme_default(func: Callable) -> Callable:
return notifyme_customized('The function is invoked.')(func)
Problem
However, as the code is interpreted by the Python interpreter, it complains that typing.Callable
is an invalid type:
Traceback (most recent call last):
File "demo.py", line 20, in <module>
def notifyme_default(func: Callable) -> Callable:
File "C:\Program Files\Python38\lib\functools.py", line 860, in register
raise TypeError(
TypeError: Invalid annotation for 'func'. typing.Callable is not a class.
I have found this issue on Python bug tracker, according to which it seems to be expected behavior since Python 3.7. Does a solution or workaround exist in Python 3.8 I use currently (or Python 3.9 that has been released recently)?
Thanks in advance.
I was unable to use
typing.Callable
withfunctools.singledispatch
, but I did find a workaround by using afunction
class reference instead: