我有一个冻结数据类的层次结构,我需要对这些数据类实现散列,以便整个层次结构中每个唯一实例的散列都是唯一的。我在这里定义“唯一”是指两个实例的字段或类型不同。但是,由于默认数据类
__hash__
仅是数据类字段的函数,而不是类型的函数,因此共享相同字段的不同数据类类型的实例默认情况下散列为相同的值。
下面是一个三层嵌套数据类。
from dataclasses import dataclass
import abc
@dataclass(frozen=True)
class Element(abc.ABC):
pass
@dataclass(frozen=True)
class StepType(Element, abc.ABC):
@classmethod
def name(cls):
return cls.__name__
class Skip(StepType): pass
class Hop(StepType): pass
@dataclass(frozen=True)
class Stepper(Element, abc.ABC):
step_type: StepType = Skip()
foo: int = 1
@abc.abstractmethod
def step(self):
pass
@dataclass(frozen=True)
class Single(Stepper):
def step(self):
return self.step_type.name() + " once"
@dataclass(frozen=True)
class Double(Stepper):
def step(self):
return self.step_type.name() + " twice"
@dataclass(frozen=True)
class Speed(Element, abc.ABC):
@abc.abstractmethod
def how_fast(self):
pass
@dataclass(frozen=True)
class Slow(Speed):
def how_fast(self):
return "real slow"
@dataclass(frozen=True)
class Fast(Speed):
def how_fast(self):
return "quickly"
@dataclass(frozen=True)
class Walker:
speed: Speed = Slow()
stepper: Stepper = Single()
def walk(self):
return " ".join([self.stepper.step(), self.speed.how_fast()])
这里总结一下
Element
的继承结构。
Element
|- StepType
| |- Skip
| |- Hop
|
|- Stepper
| |- Single
| |- Double
|
|- Speed
|- Slow
|- Fast
这里是
Walker
的嵌套字段结构的总结。
Walker
|- stepper: Stepper
| |- step_type: StepType
|
|- speed: Speed
Walker
的不同实例及其Element
实例哈希为相同的值。
a = Walker(speed=Slow(), stepper=Single())
b = Walker(speed=Fast(), stepper=Double(step_type=Hop()))
print(f"a: {hash(a)}\nb: {hash(b)}")
>> a: -5704360693866892300
b: -5704360693866892300
我希望唯一
Walker
实例的哈希值能够可靠地不同,并维护各种数据类功能的功能。
__hash__
来完成,可以通过在 Walker
和/或 Element
中创建新方法来完成。但它必须是递归的,因为 Element
实例可以包含任意深度的其他 Element
。理想情况下,我想利用可靠的默认数据类__hash__
而不是完全覆盖它。id(self)
那样依赖 object.__hash__
。Walker
之外的所有内容都必须继承自 Element
。我无法将任何数据类重构为常规类、枚举等。我必须使用数据类框架。我认为将
repr(type(self))
添加到哈希函数以及字段会很好。
我的第一个想法是这样的:
class Element:
def hash(self):
return hash(self) ^ hash(repr(type(self)))
但这不会递归到嵌套
Element
。
这是我发帖时最好的想法。
class Element:
def __hash__(self):
return hash(
hash(repr(type(self))) ^
(hash(
hash((key, val)) for key, val in self.__dict__.items()
)**3
)
)
这确实是递归的,并且它通过了我的单元测试,但它覆盖了内置数据类
__hash__
。我担心这可能会造成一些破损的极端情况。任何有关更强大方法的输入,指出该方法可能会出现问题的地方,或者简单的竖起大拇指,都是值得赞赏的。
重用数据类的内置哈希函数(该函数对所有可哈希字段进行哈希处理,同时将当前类添加到哈希函数)的一种方法是定义一个以当前类作为默认值的隐藏字段。这可以通过在基类的
__init_subclass__
方法中设置字段并在 __annotations__
属性中设置附带注释来完成:
class Element:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls._type = field(init=False, repr=False, default=cls)
cls.__annotations__['_type'] = type
演示这里