默认参数是否会覆盖 mypy 的类型提示?

Does a default parameters overwrite type hints for mypy?

以下代码按预期被 mypy 拒绝:

def foo(value: int) -> None:
    print(value, type(value))
foo(None)

输出:

error: Argument 1 to "foo" has incompatible type "None"; expected "int"

但是引入一个默认参数None后,就没有报错了:

def foo(value: int=None) -> None:
    print(value, type(value))
foo(None)

如果我们将 valueint 更改为 Optional[int],我希望 mypy 只允许 None(作为参数和默认值),但似乎不需要这样做。为什么?

当您使关键字参数接受 None 时,mypy 将隐式使该参数成为 Optional[Blah] 类型(如果尚未接受)。您可以通过在代码中添加 reveal_type(...) 函数和 运行ning mypy:

来查看这一点
def foo(value: int = None) -> None:
    print(value, type(value))

reveal_type(foo)
foo(None)

输出将是:

test.py:4: error: Revealed type is 'def (value: Union[builtins.int, None] =)'

(但请务必在实际 运行 之前删除 reveal_type,因为该函数在 运行 时实际上并不存在 - 它只是特殊情况mypy 来帮助调试。)

这种行为的存在主要是因为它有助于减少函数签名的噪音。毕竟,如果 value 在某些时候被允许为 None,显然它必须同时接受 int 和 None。在那种情况下,为什么不直接将类型推断为 Optional[int](相当于 Union[int, None],顺便说一句),这样用户就不需要重复相同的信息两次?

当然,并不是每个人都喜欢这种行为:有些人更喜欢直截了当。在这种情况下,运行 mypy 带有 --no-implicit-optional 标志。这将产生以下输出:

test.py:1: error: Incompatible default for argument "value" (default has type "None", argument has type "int")
test.py:4: error: Revealed type is 'def (value: builtins.int =)'
test.py:5: error: Argument 1 to "foo" has incompatible type "None"; expected "int"

当然,您需要更改函数签名。

如果您想以各种其他方式提高 mypy 的严格性,请尝试传递 --strict 标志。这将自动启用 --no-implicit-optional 和其他几个严格标志。更多详情,运行 mypy --help.

为@Michael0x2a 的出色回答添加一些具有历史深度的参考。 推荐的推理规则,即签名参数的默认值 None 应该使其暗示的 type 隐含地被认为是 Optional[type] 最初是在 PEP 484 中建立的,但同时发生了变化。

Union types - PEP 484

A past version of this PEP allowed type checkers to assume an optional type when the default value is None, as in this code:

def handle_employee(e: Employee = None): ...

This would have been treated as equivalent to:

def handle_employee(e: Optional[Employee] = None) -> None: ...

This is no longer the recommended behavior. Type checkers should move towards requiring the optional type to be made explicit.

如果我们看PEP 484's revision history we arrive at "GitHub's blame" which in turn gives its reasoning in Pull request #689 and references back to typing issue #275.