首页 > 解决方案 > 如何使用 Python @singledispatch 注册 typing.Callable?

问题描述

背景

假设我要实现一个简单的装饰器@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], 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)

问题

然而,由于代码是由 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 错误跟踪器上发现了这个问题,据此,它似乎是 Python 3.7 以来的预期行为。我目前使用的 Python 3.8(或最近发布的 Python 3.9)中是否存在解决方案或变通方法?

提前致谢。

标签: pythonpython-3.8callabledynamic-dispatch

解决方案


https://docs.python.org/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)

推荐阅读