functools.partial 的正确类型提示

A23*_*577 15 python functools mypy

正确的类型提示是什么functools.partial?我有一个返回 a 的函数partial,我想输入提示,这样就mypy不会抛出任何错误:

def my_func() -> ?:
    return partial(foo, bar="baz")
Run Code Online (Sandbox Code Playgroud)

比更具体typing.Callable

Jos*_*iah 23

这里有几个选择,具体取决于您的目的。

例如,我假设 foo 已定义

def foo(qux: int, thud: float, bar: str) -> str:
    # Does whatever
    return "Hi"
Run Code Online (Sandbox Code Playgroud)

如果我们使用reveal_type我们发现它partial(foo, bar="blah")被标识为functools.partial[builtins.str*]。这大致可以翻译为一个函数式的东西,它接受任何东西并返回一个字符串。因此,您可以准确地对其进行注释,并且至少可以在注释中获得返回类型。

def my_func() -> partial[str]:
    ...

a: str = my_func()(2, 2.5) # Works fine
b: int = my_func()(2, 2.5) # correctly fails, we know we don't get an int
c: str = my_func()("Hello", [12,13]) # Incorrectly passes. We don't know to reject those inputs.
Run Code Online (Sandbox Code Playgroud)

我们可以更具体一些,这样在编写函数时需要花点心思,并且可以让 MyPy 以后更好地帮助我们。一般来说,注释函数和类似函数的东西有两个主要选项。有可调用和协议。

Callable 通常更简洁,并且在处理位置参数时可以工作。协议有点详细,并且也适用于关键字参数。

因此,您可以将您的函数注释为

def my_func() -> Callable[[int, float], str]:
Run Code Online (Sandbox Code Playgroud)

也就是说,它返回一个函数,该函数接受一个 int(对于 qux)和一个 float(对于 thud)并返回一个字符串。现在,请注意,MyPy 不知道输入类型是什么,因此它无法验证该位。partial[str]将同样兼容Callable[[spam, ham, eggs], str]. 但是,它确实会顺利通过,并且如果您尝试将错误的参数传递给 Callable,它会向您发出有用的警告。那是,

my_func()(7, 2.6) # This will pass
my_func()("Hello", [12,13]) # This will now correctly fail.
Run Code Online (Sandbox Code Playgroud)

现在,我们假设定义如下foo

def foo(qux: int, bar: str, thud: float) -> str:
    # Does whatever
    return "Hi"
Run Code Online (Sandbox Code Playgroud)

一旦我们获得了作为关键字参数的partial传递bar,就无法thud作为位置参数进入。这意味着无法使用 Callable 来注释这个。相反,我们必须使用协议。

语法有点奇怪。其工作原理如下。

class PartFoo(Protocol):
    def __call__(fakeSelf, qux: int, *, thud: float) -> str:
        ...
Run Code Online (Sandbox Code Playgroud)

梳理这__call__条线,我们首先得到条目fakeSelf。这只是表示法:如果 call 是一个方法,那么第一个参数就会被吞掉。

接下来,我们像以前一样qux注释为 an int。然后,我们有*标记来指示后面的所有内容都只是关键字,因为我们无法再按thud位置到达实际方法。然后我们有thud它的注释,最后我们有-> str给出返回类型。

现在如果你定义def my_func() -> PartFoo:你就会得到我们想要的行为

my_func()(7, thud=1.5) # Works fine, qux is passed positionally, thud is a kwarg
my_func()(qux=7, thud=1.5) # Works fine, both qux and thud are kwargs
my_func()(7) # Correctly fails because thud is missing
my_func()(7, 1.5) # Correctly fails because thud can't be a positional arg.
Run Code Online (Sandbox Code Playgroud)

您可能遇到的最后一种情况是原始方法具有可选参数。那么,我们说

def foo(qux: int, bar: str, thud: float = 0.5) -> str:
    # Does whatever
    return "Hi"
Run Code Online (Sandbox Code Playgroud)

再说一次,我们无法精确地处理这个问题,CallableProtocol也很好。我们只需确保PartFoo协议还指定 的默认值thud。在这里,我使用省略号文字,作为一个温和的提醒,默认值的实际值可能在实现和协议之间有所不同。

class PartFoo(Protocol):
    def __call__(fakeSelf, qux: int, *, thud: float=...) -> str:
        ...
Run Code Online (Sandbox Code Playgroud)

现在我们的行为是

my_func()(7, thud=1.5) # Works fine, qux is passed positionally, thud is a kwarg
my_func()(qux=7, thud=1.5) # Works fine, both qux and thud are kwargs
my_func()(7) # Works fine because thud is optional
my_func()(7, 1.5) # Correctly fails because thud can't be a positional arg.
Run Code Online (Sandbox Code Playgroud)

回顾一下,部分函数返回一个相当模糊的函数类型,您可以直接使用它,但会丢失对输入的检查。您可以用更具体的内容来注释它,Callable在简单的情况和Protocol更复杂的情况下使用。