4

有几种标准方法可以使类可散列,例如(借用SO):

# 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__完成后,它的属性都是可散列的,等等)。

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

4

2 回答 2

4

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

>>> 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()可能是:

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

我们按属性名称对项目进行排序;该tuple()调用确保我们返回一个适合该hash()调用的不可变序列。

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

与您的__hash____eq__方法一起,这将为您的所有不可变类创建一个很好的基类。

于 2012-09-20T12:30:27.317 回答
1

如果您假设属性的约定,则可以这样做。在您的示例中,这将非常简单,因为您的属性以“attr_”开头-因此您可以将 __key 方法编写为:

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

如您所见 - 您可以找到的任何用于生成器表达式的过滤条件的测试都将满足您的需求。

我可以给你的一个建议是让你的类使用 Python 的__slots__ 特性:这不仅可以让你的属性名称更容易找到,还可以让你的不可变对象更高效地使用并且占用更少的内存。

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

编辑 回答OP的第一条评论:

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

于 2012-09-20T12:31:12.700 回答