Python中‘非未定义’唯一值的类型注解及mypy新惯用法咨询
问题
Python里有个惯用写法:用UNDEFINED = object()作为函数默认参数,用来区分三种场景:
- 参数未传递(触发
do1()) - 参数传
None(触发do2()) - 参数传有效值(触发
do3())
但给这个函数加mypy类型注解时,def do_something(value=int|None|object):完全没用——因为所有对象都是object类型,mypy根本没法区分;而且直接写Literal[UNDEFINED]也不合法。
目前想到的方案是定义私有类_Undefined并实例化UNDEFINED,注解写成def do_something(value=int|None|_Undefined):,但这个方案有两个坑:
- 用户可能手动传入新的
_Undefined()实例,破坏value is UNDEFINED的判断逻辑 - 必须把原来的
is判断改成isinstance,违背了原有的惯用写法
想知道现在有没有结合mypy和类型注解处理这种场景的新惯用法?
解决方案
目前社区主流的惯用法是结合私有单例类+Final标记,既满足mypy的类型检查要求,又能保留value is UNDEFINED的安全判断:
1. 带单例保护的私有哨兵类
定义私有类时加入单例逻辑,确保无论怎么调用构造方法,都只会生成一个实例;再用Final标记哨兵实例,防止被重新赋值:
from typing import Final, Union class _Undefined: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance UNDEFINED: Final[_Undefined] = _Undefined() def do_something(value: Union[int, None, _Undefined] = UNDEFINED) -> None: if value is UNDEFINED: do1() elif value is None: do2() else: do3()
这种写法下,用户即使调用_Undefined()也只会拿到和UNDEFINED一样的实例,value is UNDEFINED的判断完全有效,mypy也能正确识别类型。
2. 用类型别名简化注解
如果联合类型写起来太繁琐,可以用TypeAlias给它起个别名,让函数注解更简洁:
from typing import Final, Union, TypeAlias class _Undefined: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance UNDEFINED: Final[_Undefined] = _Undefined() ValueType: TypeAlias = Union[int, None, _Undefined] def do_something(value: ValueType = UNDEFINED) -> None: # 逻辑和之前一致 ...
3. 枚举类替代方案
另一种更简洁的方式是用enum.Enum创建单例哨兵,枚举天然保证单例,mypy对枚举类型的支持也很完善:
from enum import Enum from typing import Union, Final class Sentinel(Enum): UNDEFINED = object() UNDEFINED: Final[Sentinel] = Sentinel.UNDEFINED def do_something(value: Union[int, None, Sentinel] = UNDEFINED) -> None: if value is UNDEFINED: do1() elif value is None: do2() else: do3()
内容的提问来源于stack exchange,提问作者rrauenza
相关产品推荐
相关产品推荐

