【问题标题】:Proper type hint for functools.partialfunctools.partial 的正确类型提示
【发布时间】:2021-03-16 21:20:24
【问题描述】:

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

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

比typing.Callable更具体

【问题讨论】:

  • typing.Callable 是否足够,或者您正在寻找更具体的内容?
  • 说实话更具体...不确定是否可能

标签: python mypy functools


【解决方案1】:

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

例如,我将假设 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 和 Protocol。

当您处理位置参数时,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,就无法将thud 作为位置参数传入。这意味着没有办法使用 Callable 来注释这个。相反,我们必须使用协议。

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

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

将__call__ 行分开,我们首先有fakeSelf 条目。这只是一个符号:如果 call 是一个方法,第一个参数会被吞掉。

接下来,我们有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 协议也指定它。

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,在更复杂的情况下使用Protocol。

【讨论】:

    猜你喜欢
    • 2020-10-24
    • 2018-04-21
    • 2021-05-26
    • 2022-01-24
    • 2022-01-25
    • 1970-01-01
    • 2017-02-25
    • 2017-12-29
    • 2021-10-08
    相关资源
    最近更新 更多