如何注册类型可以使用Python @singledispatch调用吗?

问题描述

背景

假设我要实现一个简单的装饰器@notifyme,当装饰函数调用时,该装饰器将显示一条消息。我希望装饰器接受一个参数来打印定制的消息。可以省略参数(以及参数周围的括号)在这种情况下,将显示认消息:

@notifyme('Foo is invoked!')
def foo():
    pass

@notifyme  # instead of @notifyme()
def bar():
    pass

要允许省略括号,我必须提供@notifyme的两种实现:

  1. 一个实现允许用户自定义消息,因此它接受字符串作为参数并返回装饰器:

    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
    
  2. 第二种实现本身是装饰器,并使用第一种实现打印认消息:

    def notifyme_default(func: Callable) -> Callable:
        return notifyme_customized('The function is invoked.')(func)
    

要使上述两个实现使用相同的名称notifyme,我使用functools.singledispatch将对notifyme调用动态分派给两个实现之一:

# 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],**kwargs)
        return decorated_func
    return decorator

@notifyme.register
def notifyme_default(func: Callable) -> Callable:
    return notifyme_customized('The function is invoked.')(func)

问题

但是,由于Python解释器解释了代码,因此它抱怨typing.Callable是无效的类型:

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.

我在Python错误跟踪器上发现了this issue,据此看来,自python 3.7以来,这似乎是预期的行为。我当前使用的Python 3.8(或最近发布的Python 3.9)是否存在解决方案或解决方法

谢谢。

解决方法

https://docs.python.org/zh-cn/3/library/collections.abc.html#collections.abc.Callable

from collections import abc

@notifyme.register
def notifyme_default(func: abc.Callable) -> Callable:
    return notifyme_customized('The function is invoked.')(func)
,

我无法将typing.Callablefunctools.singledispatch一起使用,但确实通过使用function类引用找到了解决方法:

from functools import singledispatch
from typing import Callable

function = type(lambda: ())

@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: function) -> Callable:
    return notifyme_customized('The function is invoked.')(func)