需要修饰函数接受与绑定`TypeVar`匹配的参数,而不缩小到该类型

问题描述 投票:0回答:1

如果我像这样定义我的装饰器

T = TypeVar('T', bound=Event)

def register1(evtype: Type[T]) -> Callable[[Callable[[T], None]], Callable[[T], None]]:
    def decorator(handler):
        # register handler for event type
        return handler
    return decorator

如果我在错误的函数上使用它,我会收到正确的错误:

class A(Event):
    pass

class B(Event):
    pass

@register1(A) # Argument of type "(ev: B) -> None" cannot be assigned to parameter of type "(A) -> None"
def handler1_1(ev: B):
    pass

但是,如果我多次应用装饰器,则不起作用:

@register1(A) # Argument of type "(B) -> None" cannot be assigned to parameter of type "(A) -> None"
@register1(B)
def handler1_3(ev: A|B):
    pass

我有点希望装饰器建立一个

Union
允许/必需的参数类型。

我认为

ParamSpec
是解决这个问题的方法,但是我如何使用
ParamSpec
既不覆盖参数类型,又要求参数类型与装饰器参数中的类型匹配?

使用

ParamSpec
不会导致任何类型错误:

P = ParamSpec("P")

def register2(evtype: Type[T]) -> Callable[[Callable[P, None]], Callable[P, None]]:
    def decorator(handler):
        # ...
        return handler
    return decorator

@register2(A) # This should be an error
def handler2_1(ev: B):
    pass

如果我添加另一个

TypeVar
并使用
Union
,它确实适用于双重装饰甚至三重装饰的函数,但不适用于单装饰的函数。

T2 = TypeVar('T2')

def register3(evtype: Type[T]) -> Callable[[Callable[[Union[T,T2]], None]], Callable[[Union[T,T2]], None]]:
    def decorator(handler):
        # ...
        return handler
    return decorator

# Expected error:
@register3(A) # Argument of type "(ev: B) -> None" cannot be assigned to parameter of type "(A | T2@register3) -> None"
def handler3_1(ev: B):
    pass

# Wrong error:
@register3(A) # Argument of type "(ev: A) -> None" cannot be assigned to parameter of type "(A | T2@register3) -> None"
def handler3_2(ev: A):
    pass

# Works fine
@register3(A)
@register3(B)
def handler3_3(ev: A|B):
    pass

在写这个问题的时候,我离答案越来越近了。 我将在答案中提供我自己的解决方案。

但是,我很感兴趣是否有更好的方法来解决这个问题。

python mypy python-decorators python-typing pyright
1个回答
0
投票

通过使用

Union
添加装饰函数仅接受装饰器参数的单个参数的情况,我不再从
pyright
收到任何意外错误:

def register4(evtype: Type[T]) -> Callable[[Union[Callable[[T|T2], None],Callable[[T], None]]], Callable[[T|T2], None]]:
    def decorator(handler):
        # ...
        return handler
    return decorator

#Expected errors
@register4(A) # Argument of type "(ev: B) -> None" cannot be assigned to parameter of type "((A | T2@register4) -> None) | ((A) -> None)"
def handler4_1(ev: B):
    pass

@register4(A)
def handler4_2(ev: A):
    pass

@register4(A)
@register4(B)
#@register4(C)
def handler4_3(ev: A|B|C):
    pass
© www.soinside.com 2019 - 2024. All rights reserved.