如何使用 None 作为非可选变量的占位符而 mypy 不会抱怨它?

How to use None as a placeholder for a non optional variable without mypy complaining about it?

直接上代码:

from typing import Optional, List


class Bar:
    pass


class Foo:
    def __init__(self, bar_list: List[Optional[Bar]]) -> None:
        self.bar_list: List[Bar] = bar_list # mypy will signal an error here

        for i, bar in enumerate(self.bar_list):
            if bar is None:
                self.bar_list[i] = self.__default_bar()

    def __default_bar(self):
        # create default
        pass

mypy 这样做很有意义,但是我想明确指出,在使用此 class 时,永远不应期望 foo.barl_list 具有 None 元素,同时提供仅分配一些自定义值并让构造函数为其余值添加默认值的灵活性。

那么如何在不触发 mypy 错误的情况下保留此功能?一种方法是将 self.bar_list 初始化为一个空列表,然后附加所有值,但这会导致列表重新分配成本,这在大列表的情况下可能会很大,因此我正在寻找替代方案。

您尝试做的事情与 List[Bar]static 类型根本不兼容。但是,您可以通过 casting 将列表作为不同的类型告诉静态类型检查器您知道自己在做什么。您可以通过将每个 None 替换为 Bar 的实际实例来维护不变性。 (需要明确的是,类型检查器无法帮助您验证您是否确实这样做了。)

def __init__(self, bar_list: List[Optional[Bar]]) -> None:
    self.bar_list: List[Bar] = typing.cast(List[Bar], bar_list)

    for i, bar in enumerate(self.bar_list):
        if bar is None:
            self.bar_list[i] = self.__default_bar()

正如您所注意到的,MyPy 对此的抱怨是完全正确的。当您在前一行中告诉它“小心,因为这可能包含 None”时,您明确地说“这将不包含 None”。您希望表达的是“这(最终)不会包含 None”,但这并不是真正的 MyPy 风格。如果您希望干净地完成它,则该类型必须在创建时有效。只有这样,MyPy 才能帮助确保它保持有效。

我只是用列表理解来做这个。像

self.bar_list: List[Bar] = [
    b if b is not None else self.__default_bar() for b in bar_list
]

尽管这意味着要重新列出一份清单,但我强烈建议您将其视为加分项而不是减分项。改变函数的输入通常被认为是非常糟糕的形式,除非这是该函数的唯一明确目的。