关于python:自动使类可哈希

Automatically making a class hashable

有几种标准方法可以使类具有哈希性,例如(借用so):

1
2
3
4
5
6
7
8
9
10
# assume X has 2 attributes: attr_a and attr_b
class X:
  def __key(self):
    return (self.attr_a, self.attr_b)

  def __eq__(x, y):
    return isinstance(y, x.__class__) and x.__key() == y.__key()

  def __hash__(self):
    return hash(self.__key())

现在假设我有许多类,我想让它们成为可散列的。它们都是不可变的,具有不可变的属性,并且批量散列所有这些属性是可以接受的(对于属性太多的类,我们只希望散列一些足以避免大多数冲突的属性)。我可以避免为每个类手工编写__key()方法吗?

为它们定义__key()__eq____hash__是一个好主意吗?特别是,我不确定是否可以找到应该进入__hash__的所有实例属性。我知道这通常是不可能的,但是在这种情况下,我们可以对对象进行更多的假设(例如,它是不可变的-在__init__完成之后,它的属性都是可哈希的,等等)。

(如果继承层次结构不起作用,可能是装饰器会起作用?)


实例将其属性存储在self.__dict__中:

1
2
3
4
5
6
7
8
>>> class Foo(object):
...     def __init__(self, foo='bar', spam='eggs'):
...         self.foo = foo
...         self.spam = spam
...
>>> f = Foo()
>>> f.__dict__
{'foo': 'bar', 'spam': 'eggs'}

如果您不在实例上存储任何方法,默认的.__key()可以是:

1
2
def __key(self):
    return tuple(v for k, v in sorted(self.__dict__.items()))

在这里,我们按照属性名对项目进行排序;tuple()调用确保返回适合hash()调用的不可变序列。

对于更复杂的设置,您必须测试EDOCX1(跳过函数等)返回的类型,或者使用特定的属性模式,或者重新调整__slots__的用途以列出可以使用的适当属性。

再加上您的__hash____eq__方法,对于所有不变的类来说,这将是一个很好的基类来继承。


如果为属性假定约定,则可以这样做。在你的例子中,这将是非常简单的,因为您的属性以"attr_uuu"开头,所以您可以将uu键方法编写为:

1
2
def __key(self):
    return tuple (getattr(self, attr) for attr in self.__dict__ if attr.startswith("attr_") )

正如您所看到的,任何您可以找到的对生成器表达式的筛选条件进行测试都将符合您的需要。

我可以给您一个建议,让您的类使用python的__slots__特性:这不仅使属性名易于查找,而且使不可变对象的使用效率更高,内存占用也更小。

1
2
3
4
class X:
    __slots__ = ("a","b","c")
    def __key(self):
        return tuple (getattr(self, attr) for attr in self.__class__.__slots__ )

编辑回答O.P.的第一条评论:

当然,这与继承有关。如果您总是为它们使用对象的所有属性,则不需要表达式的"if"部分-在层次结构顶部的一个类上以_key的形式编写函数(而不是以__key的形式在内部为每个类创建唯一的名称),它将适用于所有类。