pycharm 在Python中为Callables定义变量参数类型提示

b1zrtrql  于 2023-08-05  发布在  PyCharm
关注(0)|答案(1)|浏览(156)

我有一个类,它可能包含一个将在其他地方调用的函数。根据调用它的内容,它可以有可变数量的参数。一个所有者可能使用参数(event, int, str)调用它,另一个所有者可能使用(event, str, bool, dict)调用它。稍后会进行验证,以确保签名与所有者所需的签名相匹配。出于类型提示的目的,我需要确保传递的签名匹配以Event对象开始的任何内容,以及fine和dandy之后的任何内容。因此,像def foo(event: Event, a: int, b: bool)def (event: ClickEvent, c: dict, *args, **kwargs) -> typing.Coroutine[Any, Any, str]这样的函数都绝对有效。
给出下面的例子:

from typing import *
from dataclasses import dataclass

PARAMS = ParamSpec("PARAMS")

@dataclass
class Event:
    field1: int
    field2: bool
    name: str

    def get_name(self) -> str:
        return self.name

class SomeCaller:
    ...

class Element:
    ...

class ClickEvent(Event):
    def __init__(self, field1: int, field2: int, target: str):
        super().__init__(field1=field1, field2=field2, name="click")
        self.__target = target

    @property
    def target(self) -> str:
        return self.__target

# The type hint in question
HANDLER = Callable[
    [
        Event,
        PARAMS
    ], Union[Any, Coroutine]
]

def control(event: Event, *args, **kwargs) -> bool:
    pass

def false_control(event: ClickEvent, *args, **kwargs) -> bool:
    pass

async def async_function(event: Event, arg1: int, arg2: int, *args, **kwargs):
    return 9

def function(event: ClickEvent, arg1: int, arg2: int, *args, **kwargs):
    return 7

def other_function(event: Event, caller: SomeCaller, element: Element):
    return 8

class EventHandlerWhatsit:
    def __init__(self, handler: HANDLER):
        self.__handler = handler
        

control_value = EventHandlerWhatsit(control)
false_control_value = EventHandlerWhatsit(false_control)
async_function_value = EventHandlerWhatsit(async_function)
function_value = EventHandlerWhatsit(function)
other_function_value = EventHandlerWhatsit(other_function)

def main():
    print("This works")

if __name__ == "__main__":
    main()

字符串
false_control_valueasync_function_valuefunction_valueother_function_value的声明中会出现键入提示警告,所有这些都带有类似Expected type '(Event, ParamSpec("PARAMS")) -> Coroutine | Any' (matched generic type '(Event, ParamSpec("PARAMS")) -> Coroutine | Any'), got '(event: Event, arg1: int, arg2: int, args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any' instead的警告。control_value的声明没有问题。在EventHandlerWhatsit的初始化器中对self.__handler = handler的赋值也显示了Expected type '(Event, ParamSpec("PARAMS")) -> Coroutine | Any', got '(Event, ParamSpec("PARAMS")) -> Coroutine | Any' instead的警告,我觉得这很奇怪。
它只需要指出“只要以Event的子类作为参数开始,就可以调用的东西”。名字不重要。我已经用各种方式尝试了HANDLER的定义,例如将*args**kwargs定义为Tuple[Any, ...]Dict[str, Any](有和没有Optional),使用额外的参数,但最终仍然得到相同类型的警告。
我被python 3.8卡住了,我在PyCharm中编辑,它显示了警告。
有什么想法吗

编辑

@Daniil Fajnberg和@SUTerliakov在评论中提供了完美的答案:

HANDLER = Callable[
    Concatenate[
        Event,
        PARAMS
    ], Union[Any, Coroutine]
]


Concatenate允许注解匹配与输入定义中稍有不同的值。
下面的代码是无效的:

def test_inner(arg: Callable[[str, int, P], Any]):
    pass

def test_input(i: str, j: int, q: str = None, *args, **kwargs):
    pass

def test_outer():
    test_inner(test_input)


linter将触发警告,因为可选的q参数的存在不符合test_innerarg参数的预期。但是,将test_inner中的arg的定义更改为arg: Callable[Concatenate[str, int, P], Any],linter就可以了。

警告:ParamSpecConcatenate是在Python 3.10中引入的。如果由于环境限制而需要使用旧版本,请使用typing_extensions包来提供该功能。

mkh04yzy

mkh04yzy1#

@Daniil Fajnberg和@SUTerliakov在评论中提供了完美的答案:

HANDLER = Callable[
    Concatenate[
        Event,
        PARAMS
    ], Union[Any, Coroutine]
]

字符串
Concatenate允许注解匹配与输入定义中稍有不同的值。
下面的代码是无效的:

def test_inner(arg: Callable[[str, int, P], Any]):
    pass

def test_input(i: str, j: int, q: str = None, *args, **kwargs):
    pass

def test_outer():
    test_inner(test_input)


linter将触发警告,因为可选q参数的存在不符合test_innerarg参数的预期。但是,将test_inner中的arg的定义更改为arg: Callable[Concatenate[str, int, P], Any],linter就可以了。

警告:ParamSpecConcatenate是在Python 3.10中引入的。如果由于环境限制而需要使用旧版本,请使用typing_extensions包来提供该功能。

相关问题