如何键入可变默认参数

How to type mutable default arguments

在Python中处理可变默认参数的方法是set them to None

例如:

def foo(bar=None):
    bar = [] if bar is None else bar
    return sorted(bar)

如果我输入函数定义,那么 bar 的唯一类型表示 barOptional,而显然,它不是 Optional我希望 运行 那个 sorted 函数在它上面的时间:

def foo(bar: Optional[List[int]]=None):
    bar = [] if bar is None else bar
    return sorted(bar) # bar cannot be `None` here

那我应该投吗?

def foo(bar: Optional[List[int]]=None):
    bar = [] if bar is None else bar
    bar = cast(List[int], bar) # make it explicit that `bar` cannot be `None`
    return sorted(bar)

我是否应该只希望通读函数的人看到处理默认可变参数的标准模式,并理解对于函数的其余部分,参数不应该是 Optional

处理此问题的最佳方法是什么?

编辑: 澄清一下,此函数的用户应该能够将 foo 调用为 foo()foo(None)foo(bar=None)。 (我不认为以任何其他方式拥有它是有意义的。)

编辑#2: Mypy 会 run with no errors if you never type bar as Optional and instead only type it as List[int], despite the default value being None. However, this is highly not recommended because this behavior may change in the future, and it also implicitly types the parameter as Optional. (See 了解详情。)

为什么不在影子bar:

时剪掉演员表
def foo(bar: Optional[List[int]]=None):
    bar : List[int] = [] if bar is None else bar
    return sorted(bar)

如果您将默认值添加到函数中的参数而不是它是可选的。 同样在您的代码中,您允许调用者不向该函数提供任何内容,它仍然有效,因为它只是创建一个空列表。

同样在 python 中,即使键入它也不会强制执行类型。这就是它在文档中称为“类型提示”的原因。

所以如果你想让调用者调用不带参数的函数并且它只是对一个空列表进行排序那么你的代码是正确的。

def foo(bar=None):
    bar = [] if bar is None else bar
    return sorted(bar)

但是如果你永远不希望调用者不提供任何东西,甚至 None,那么你应该改变你的函数签名。

def foo(bar: List[int]):
    bar = [] if bar is None else bar
    return sorted(bar)

现在类型提示检测到 barList[int]。你必须传递一些东西给 foo。所以你可以做 foo(None) 这就是为什么你需要 None 检查但现在 foo() 无效并抛出错误。

如果您确实希望他们不传递任何内容,那么只需执行此操作,类型提示仍然有效。

def foo(bar: List[int]=None):
   bar = [] if bar is None else bar
   return sorted(bar)

None 不是唯一可用的哨兵。您可以选择 自己的 列表值用作标记​​,在 运行 时用新的空列表替换它(而不是 None)。

_sentinel = []

def foo(bar: List[int]=_sentinel):
    bar = [] if bar is _sentinel else bar
    return sorted(bar)

只要没有人使用 _sentinel 作为显式参数调用 foobar 将始终得到一个新的空列表。在像 foo([]) 这样的调用中,bar is _sentinel 将为假:两个空列表不是同一个对象,因为列表的可变性意味着你不能有一个总是被 [= 引用的空列表18=].

我不确定这里有什么问题,因为在 mypy 中使用 Optional[List[int]] 作为类型非常好:https://mypy-play.net/?mypy=latest&python=3.9&gist=2ee728ee903cbd0adea144ce66efe3ab

在你的例子中,当 mypy 看到 bar = [] if bar is None else bar 时,它足够聪明地意识到 bar 不能超过这一点 None,从而将类型缩小到 List[int].在此处阅读有关 mypy 中类型缩小的更多信息:https://mypy.readthedocs.io/en/stable/kinds_of_types.html?highlight=narrow#union-types

这里是 some other examples 类型的缩小:

from typing import *

a: Optional[int]
assert a is not None
reveal_type(a)  # builtins.int

b: Union[int, float, str]
if isinstance(b, int):
    reveal_type(b)  # builtins.int
else:
    reveal_type(b)  # Union[builtins.float, builtins.str]

使用 Sequence[int] 怎么样?将对参数(包括其默认值)几乎 readonly.

进行类型检查
from collections.abc import Sequence

def foo(bar: Sequence[int] = []) -> list[int]:
    return sorted(bar)

还有bar可以如下变异,但是风险不会很高。 (_sentinel: list[int] = [] 也可以变异。)

def foo(bar: Sequence[int] = []) -> list[int]:
    if isinstance(bar, list):
        # reveal_type(bar)  # => Revealed type is "builtins.list[Any]"
        bar.append(0)
    return sorted(bar)