如何在Python中实现支持match语句的代数数据类型单例?
模拟Kotlin密封类的Python实现方案
问题背景
在支持代数数据类型的语言(如Kotlin)中,常用密封类实现分层数据结构:
sealed class Thing { data class CoolThing(val coolness: Int) : Thing() data class OtherThing(val other: String) : Thing() object BoringThing : Thing() }
尝试在Python中模拟时,写出如下代码:
from dataclasses import dataclass from typing import Literal @dataclass class CoolThing: coolness: int @dataclass class OtherThing: other: str BoringThing = object() Thing = CoolThing | OtherThing | Literal[BoringThing]
但存在两个问题:
- 大量单例对象需用
Literal包裹,操作繁琐;自定义单例类后,无法让对象兼具元类与类的特性 match语句对单例对象的匹配不符合预期,绑定的是名称而非值
期望实现的效果:
from dataclasses import dataclass # <Some kind of declaration of SingletonObject> @dataclass class CoolThing: coolness: int @dataclass class OtherThing: other: str BoringThing = SingletonObject() Thing = CoolThing | OtherThing | BoringThing # 支持如下匹配(假设thing是Thing类型): match thing: case BoringThing: print('This is boring') case OtherThing(other): print(f'This is {other}') case CoolThing(coolness): print(f'This is {coolness} cool')
可行解决方案
方案1:自定义可匹配的Singleton类
通过自定义单例类,让实例能在match语句中按值匹配,同时支持类型注解:
from dataclasses import dataclass from typing import Union, TypeVar, Generic T = TypeVar('T') class SingletonObject(Generic[T]): def __init__(self, name: str): self.name = name def __repr__(self): return self.name # 让match语句能匹配值而非名称 def __eq__(self, other): return isinstance(other, SingletonObject) and self.name == other.name # 支持类型检查 def __class_getitem__(cls, item): return cls # 定义单例实例 BoringThing = SingletonObject("BoringThing") SleepyThing = SingletonObject("SleepyThing") @dataclass class CoolThing: coolness: int @dataclass class OtherThing: other: str # 定义联合类型 Thing = Union[CoolThing, OtherThing, SingletonObject] # 测试匹配 def test_match(thing: Thing): match thing: case BoringThing: print('This is boring') case SleepyThing: print('This is sleepy') case OtherThing(other): print(f'This is {other}') case CoolThing(coolness): print(f'This is {coolness} cool') test_match(BoringThing) # 输出: This is boring test_match(OtherThing("test")) # 输出: This is test
方案2:使用Enum实现单例+数据类组合
利用Enum天然的单例特性,结合数据类实现密封类效果:
from dataclasses import dataclass from enum import Enum from typing import Union # 定义单例枚举 class SingletonThing(Enum): BORING_THING = "BoringThing" SLEEPY_THING = "SleepyThing" def __repr__(self): return self.value @dataclass class CoolThing: coolness: int @dataclass class OtherThing: other: str # 定义联合类型 Thing = Union[CoolThing, OtherThing, SingletonThing] # 测试匹配 def test_match(thing: Thing): match thing: case SingletonThing.BORING_THING: print('This is boring') case SingletonThing.SLEEPY_THING: print('This is sleepy') case OtherThing(other): print(f'This is {other}') case CoolThing(coolness): print(f'This is {coolness} cool') test_match(SingletonThing.BORING_THING) # 输出: This is boring test_match(CoolThing(10)) # 输出: This is 10 cool
方案3:用dataclass实现单例对象
通过dataclass(frozen=True)和__new__方法创建单例,让实例可被match语句匹配:
from dataclasses import dataclass from typing import Union @dataclass(frozen=True) class SingletonObject: _instance = None name: str def __new__(cls, name: str): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance # 定义单例 BoringThing = SingletonObject("BoringThing") @dataclass class CoolThing: coolness: int @dataclass class OtherThing: other: str Thing = Union[CoolThing, OtherThing, SingletonObject] # 测试匹配 def test_match(thing: Thing): match thing: case BoringThing: print('This is boring') case OtherThing(other): print(f'This is {other}') case CoolThing(coolness): print(f'This is {coolness} cool') test_match(BoringThing) # 输出: This is boring
内容的提问来源于stack exchange,提问作者LambdaBeta
相关产品推荐
相关产品推荐

