Python中用于带有kwargs参数的函数的typing.Callable类型签名。

77

我经常使用 Python 3 的类型支持。

最近,我尝试将函数作为参数传递,但是在 typing.Callable 签名中,我没有找到如何使用 kwargs 的帮助。

请查看以下代码和注释。

import typing

# some function with a type signature
def fn1_as_arg_with_kwargs(a: int, b: float) -> float:
    return a + b

# some function with a type signature
def fn2_as_arg_with_kwargs(a: int, b: float) -> float:
    return a * b

# function that get callables as arg
# this works with typing
def function_executor(
        a: int, 
        b: float, 
        fn: typing.Callable[[int, float], float]):
    return fn(a, b)

# But what if I want to name my kwargs 
# (something like below which does not work)
# ... this will help me more complex scenarios 
# ... or am I expecting a lot from python3 ;)
def function_executor(
        a: int, 
        b: float, 
        fn: typing.Callable[["a": int, "b": float], float]):
    return fn(a=a, b=b)

当您定义一个函数时,您指定其签名,现在包括参数的类型。指定字典结构作为参数几乎没有价值,特别是 **kwargs,它只是收集命名参数,即它取决于函数的调用方式。使用其他适当和具体的类型(例如NamedTuple、Dataclass、Enum、自定义类),也就是像通常一样明确每个参数。 - Pynchia
我在某些情况下使用NamedTuple作为参数,这在某些场景下是非常有用的。但是我很好奇上述内容是否被支持,因为我需要对一些旧函数进行typedef。如果完全不支持,请告诉我,因为那可能更有帮助。 - Praveen Kulkarni
这个?https://github.com/python/typing/issues/239 - shawn
2个回答

110

你可能正在寻找回调协议

简而言之,当您想要表达具有复杂签名的可调用对象时,您需要创建一个自定义协议,该协议定义了一个带有所需精确签名的__call__方法。

例如,在您的情况下:

from typing import Protocol

# Or, if you want to support Python 3.7 and below, install the typing_extensions
# module via pip and do the below:
from typing_extensions import Protocol

class MyCallable(Protocol):
    def __call__(self, a: int, b: float) -> float: ...

def good(a: int, b: float) -> float: ...

def bad(x: int, y: float) -> float: ...


def function_executor(a: int, b: float, fn: MyCallable) -> float:
    return fn(a=a, b=b)

function_executor(1, 2.3, good)  # Ok!
function_executor(1, 2.3, bad)   # Errors

如果你使用mypy对这个程序进行类型检查,最后一行会出现以下(确实有点难懂的)错误信息:

Argument 3 to "function_executor" has incompatible type "Callable[[int, float], float]"; expected "MyCallable"

(回调协议有些新,所以希望随着时间的推移错误消息的质量会得到改善。)


1
你使用的是哪个版本的mypy?这个例子在mypy 1.2.0或1.1.1中似乎没有为“坏”情况抛出错误消息。 - laker93

9

我觉得带有键值参数回调的示例有点复杂。对于任何想要查找一个简单的带有kwargs函数类型的示例的人:

from typing import Protocol

class MyCallable(Protocol):
    # Define types here, as if __call__ were a function (ignore self).
    def __call__(self, a: int, b: int) -> int:
        ...

# Generic function- types correspond to MyCallable.__call__ args.
def func_add(a: int, b: int) -> int:
    return a + b

# Assign the function to a variable called my_function, and add the type.
my_function: MyCallable = func_add

my_function(a=1, b=2)   # This is OK.
my_function(a=1, b="x") # This is NOK.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接