如何创建可同时作为类型与值使用的单例对象(类似None)
实现可用于类型提示的自定义NotSet值
我希望定义一个自定义的NotSet值,能像None一样直接用于类型提示,示例代码期望如下:
NotSet = ? class Client: def partial_update( self, obj_id: int, obj_field: int | None | NotSet = NotSet, ... ): # 不更新未显式指定的字段 if obj_field is NotSet: # 不更新obj_field else: # 更新obj_field ...
不够理想的解决方案
1. 使用None
- 问题:
None本身已用于业务逻辑(字段支持可空),无法区分“未指定字段”和“指定字段为null”两种场景。
2. 使用内置ellipsis
from types import EllipsisType def partial_update( obj_field: int | None | EllipsisType = ..., ): pass
- 缺点:
- 语义不明确,
...通常用于省略参数或表示任意类型,无法直观体现“未设置”的含义; - 无法直接写
int | None | ...作为类型提示,必须引用EllipsisType。
- 语义不明确,
3. 使用自定义单例类
class NotSetType: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance NotSet = NotSetType() def partial_update( obj_field: int | None | NotSetType = NotSet, ): pass
- 缺点:这是最接近需求的方案,但类型提示必须写
NotSetType,无法直接使用NotSet,不符合“像None一样”的预期。
改进后的最优方案
我们可以通过类型别名结合单例模式,让NotSet同时作为单例实例和类型提示标识:
from typing import TypeAlias class NotSetType: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance def __repr__(self) -> str: return "NotSet" # 让NotSet既是单例实例,又是指向NotSetType的类型别名 NotSet: TypeAlias = NotSetType() # 验证类型提示和运行逻辑 class Client: def partial_update( self, obj_id: int, obj_field: int | None | NotSet = NotSet, ): if obj_field is NotSet: print("不更新obj_field") else: print(f"更新obj_field为{obj_field}") # 测试调用 client = Client() client.partial_update(1) # 输出:不更新obj_field client.partial_update(1, obj_field=None) # 输出:更新obj_field为None client.partial_update(1, obj_field=5) # 输出:更新obj_field为5
在Python 3.12+中,还可以用更简洁的type语句定义类型别名:
class NotSetType: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance NotSet = NotSetType() type NotSet = NotSetType
这样做的好处:
- 类型提示中直接写
NotSet即可,和None的使用方式完全一致; - 运行时通过
is判断obj_field is NotSet能准确识别未设置的字段; - 语义清晰,
NotSet明确表示“字段未被显式指定”,和业务逻辑中的None(字段设为null)完全区分。
内容的提问来源于stack exchange,提问作者Mikhail M.
相关产品推荐
相关产品推荐

