在数据类哈希函数中包含类型

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

我有一个冻结数据类的层次结构,我需要对这些数据类实现散列,以便整个层次结构中每个唯一实例的散列都是唯一的。我在这里定义“唯一”是指两个实例的字段或类型不同。但是,由于默认数据类

__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))
添加到哈希函数以及字段会很好。

想法1

我的第一个想法是这样的:

class Element:
    def hash(self):
        return hash(self) ^ hash(repr(type(self)))

但这不会递归到嵌套

Element

想法2

这是我发帖时最好的想法。

class Element:
    def __hash__(self):
        return hash(
            hash(repr(type(self))) ^ 
            (hash(
                hash((key, val)) for key, val in self.__dict__.items()
                )**3
            )
        )

这确实是递归的,并且它通过了我的单元测试,但它覆盖了内置数据类

__hash__
。我担心这可能会造成一些破损的极端情况。任何有关更强大方法的输入,指出该方法可能会出现问题的地方,或者简单的竖起大拇指,都是值得赞赏的。

python hash python-dataclasses
1个回答
0
投票

重用数据类的内置哈希函数(该函数对所有可哈希字段进行哈希处理,同时将当前类添加到哈希函数)的一种方法是定义一个以当前类作为默认值的隐藏字段。这可以通过在基类的

__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

演示这里

© www.soinside.com 2019 - 2024. All rights reserved.