functools.partial 的正确类型提示

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

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

def my_func() -> ?:
    return partial(foo, bar="baz")

typing.Callable

更具体
python python-typing mypy functools
1个回答
25
投票

这里有几个选项,具体取决于您想要什么。

我假设 foo 已被定义

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

如果我们使用

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.

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

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

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

def my_func() -> Callable[[int, float], str]:

也就是说,它返回一个函数,该函数接受一个 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.

现在,我们假设

foo
定义如下。

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

一旦我们将

partial
作为关键字参数传递,就无法将
bar
作为位置参数传入。这意味着无法使用 Callable 来注释这个。相反,我们必须使用协议。
语法有点奇怪。其工作原理如下。

thud

拆开 
class PartFoo(Protocol): def __call__(fakeSelf, qux: int, *, thud: float) -> str: ...

行,我们首先得到

__call__
条目。这只是表示法:如果 call 是一个方法,第一个参数就会被吞掉。
接下来,我们有 

fakeSelf

,如之前一样注释为

qux
。然后,我们使用
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.

再一次,我们无法用 
def foo(qux: int, bar: str, thud: float = 0.5) -> str: # Does whatever return "Hi"

精确处理这个问题,但

Callable
就可以了。我们只需确保
Protocol
协议也为
PartFoo
指定默认值。在这里,我使用省略号文字,作为一个温和的提醒,默认值的实际值可能在实现和协议之间有所不同。
thud

现在我们的行为是

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

回顾一下,偏函数返回一个相当模糊的函数类型,您可以直接使用它,但会丢失对输入的检查。您可以用更具体的内容对其进行注释,在简单的情况下使用 
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.

,在更复杂的情况下使用

Callable
    

© www.soinside.com 2019 - 2024. All rights reserved.