functools.partial
的正确类型提示是什么?我有一个返回 partial
的函数,我想输入提示,这样 mypy
就不会抛出任何错误:
def my_func() -> ?:
return partial(foo, bar="baz")
比
typing.Callable
更具体
这里有几个选项,具体取决于您想要什么。
我假设 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
。