以标记值作为默认值的类型提示参数

问题描述 投票:0回答:3

当我无法在函数签名中分配默认参数和/或 None 已经有意义时,我当前使用

此策略

from typing import Optional

DEFAULT = object()


# `None` already has meaning.
def spam(ham: Optional[list[str]] = DEFAULT):
    if ham is DEFAULT:
        ham = ['prosciutto', 'jamon']
    if ham is None:
        print('Eggs?')
    else:
        print(str(len(ham)) + ' ham(s).')

错误:

Failed (exit code: 1) (2607 ms)

main.py:7: error: Incompatible default for argument "ham" (default has type "object", argument has type "Optional[List[str]]")
Found 1 error in 1 file (checked 1 source file)
  • 如何输入提示
    ham
    而不会在 mypy 中出现错误?
  • 我应该使用什么策略来代替
    DEFAULT = object()
python mypy python-typing
3个回答
12
投票

正如我所评论的,这是 Python 的一个活跃的开发领域。 PEP 661 建议添加一个

sentinel
函数来创建哨兵对象,但在该 PEP 获得批准之前,您只能靠自己了。

不过,您可以从 PEP 中提出的(或拒绝的)一些选项中获得灵感。一种非常简单的方法,可以很好地配合类型提示,就是让你的哨兵值成为一个类:

class DEFAULT: pass

现在您可以对函数进行类型提示,将其作为包含

type[DEFAULT]
的类型的并集:

def spam(ham: list[str]|None|type[DEFAULT] = DEFAULT):

6
投票

我喜欢做的事情 - 这只是 @Blckknght 的答案的一个微小变化 - 是使用元类为我的哨兵类提供更好的表示并使其始终错误。


哨兵.py

from typing import Literal 

class SentinelMeta(type):
    def __repr__(cls) -> str:
        return f'<{cls.__name__}>'

    def __bool__(cls) -> Literal[False]:
        return False


class Sentinel(metaclass=SentinelMeta): pass

main.py

from sentinel import Sentinel

class DEFAULT(Sentinel): pass

您在类型提示中使用它的方式与@Blckknght 建议的方式完全相同:

def spam(ham: list[str]|None|type[DEFAULT] = DEFAULT): ...

但是您还有一个额外的优势,即您的哨兵值总是错误的并且具有更好的表现:

>>> DEFAULT
<DEFAULT>
>>> bool(DEFAULT)
False

4
投票

现有答案没有任何问题,但我想说另一个有效的选择是使用

Enum
。事实上,这是PEP484 - 支持联合中的单例类型中推荐的方法:

为了在这种情况下进行精确键入,用户应将

Union
类型与标准库提供的
enum.Enum
类结合使用,以便可以静态捕获类型错误。

您的代码应该如下所示:

from enum import Enum

class Default(Enum):
    token = 0

DEFAULT = Default.token

def spam(ham: list[str] | None | Default = DEFAULT):
    if ham is DEFAULT:
        ham = ["prosciutto", "jamon"]
    if ham is None:
        print("Eggs?")
    else:
        print(str(len(ham)) + " ham(s).")
© www.soinside.com 2019 - 2024. All rights reserved.