Python:自定义可变 class 可以用作字典的键吗?
Python: Can a custom mutable class be used as a key for a dictionary?
假设我们有一个像这样的自定义节点 class:
class Node:
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
我有一个节点对象,我想将其用作字典的键。
我的理解是,一个对象应该是不可变的和可哈希的,才能可靠地用作字典键,因为可变对象可能会导致哈希值发生变化,而这个对象是可变的。
我知道 python 允许将自定义可变对象用作字典键,这是如何工作的?
更新:引用的链接不涉及自定义对象的可变性方面。它们只是提供了一种方法来覆盖散列函数的默认实现。这个问题应该重新打开,因为它与引用的 "duplicate" 个问题不同。
答案:自定义可变对象的散列方法的默认实现使用identity,它保证在对象的生命周期内是唯一且不变的。一个可变的自定义对象不应该覆盖哈希函数的默认实现。下面提供了更详细的答案。
节点自定义对象根据其 class 定义是可变的,任何可变对象不应覆盖散列方法的默认实现,因为默认方法使用 identity ,保证在对象的生命周期内是唯一且不变的。这样做 可能会 导致对象的哈希值发生变化,这会导致很多问题。
例如:
Node node = new Node(5, None, None)
cache = {}
cache[node] = "Node added"
node.val = 5
# If we override default implementation of hash function, we have to
# make sure hashing function does not use object attributes else
# accessing cache[node] now would cause problem as a different hashed
# value would be computed and we won't be able to retrieve "Node added"
# value.
# Default implementation uses identity and would always hash
# to same value even if the object mutates.
但是我们可以继续为不可变对象覆盖散列方法。 this 线程上的回答帮助我理解了它是如何工作的。对于以下内容,我们将假设 Node 对象的属性一旦创建就永远不会改变。
这里的想法是确保您的自定义对象实现散列和相等方法。
相等方法的实现应使两个逻辑等价对象始终被视为相等。对于此处给定的 Node 对象,如果两个节点的所有属性都相同,则认为两个节点相等。所以我们可以构建一个键,它是所有属性的元组。这是可行的,因为元组是不可变的,所以我们基本上是在创建一个包含对象所有属性的不可变对象,并将使用它来计算哈希值。
以下实现确保两个逻辑上等效的节点(具有相同的属性)具有相同的键,因此将为两个相等的节点生成相同的哈希值。
class Node:
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
def __key(self):
return (self.val, self.next, self.random)
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
if isinstance(other, Node):
return self.__key() == other.__key()
return NotImplemented
假设我们有一个像这样的自定义节点 class:
class Node:
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
我有一个节点对象,我想将其用作字典的键。
我的理解是,一个对象应该是不可变的和可哈希的,才能可靠地用作字典键,因为可变对象可能会导致哈希值发生变化,而这个对象是可变的。
我知道 python 允许将自定义可变对象用作字典键,这是如何工作的?
更新:引用的链接不涉及自定义对象的可变性方面。它们只是提供了一种方法来覆盖散列函数的默认实现。这个问题应该重新打开,因为它与引用的 "duplicate" 个问题不同。
答案:自定义可变对象的散列方法的默认实现使用identity,它保证在对象的生命周期内是唯一且不变的。一个可变的自定义对象不应该覆盖哈希函数的默认实现。下面提供了更详细的答案。
节点自定义对象根据其 class 定义是可变的,任何可变对象不应覆盖散列方法的默认实现,因为默认方法使用 identity ,保证在对象的生命周期内是唯一且不变的。这样做 可能会 导致对象的哈希值发生变化,这会导致很多问题。
例如:
Node node = new Node(5, None, None)
cache = {}
cache[node] = "Node added"
node.val = 5
# If we override default implementation of hash function, we have to
# make sure hashing function does not use object attributes else
# accessing cache[node] now would cause problem as a different hashed
# value would be computed and we won't be able to retrieve "Node added"
# value.
# Default implementation uses identity and would always hash
# to same value even if the object mutates.
但是我们可以继续为不可变对象覆盖散列方法。 this 线程上的回答帮助我理解了它是如何工作的。对于以下内容,我们将假设 Node 对象的属性一旦创建就永远不会改变。
这里的想法是确保您的自定义对象实现散列和相等方法。
相等方法的实现应使两个逻辑等价对象始终被视为相等。对于此处给定的 Node 对象,如果两个节点的所有属性都相同,则认为两个节点相等。所以我们可以构建一个键,它是所有属性的元组。这是可行的,因为元组是不可变的,所以我们基本上是在创建一个包含对象所有属性的不可变对象,并将使用它来计算哈希值。
以下实现确保两个逻辑上等效的节点(具有相同的属性)具有相同的键,因此将为两个相等的节点生成相同的哈希值。
class Node:
def __init__(self, val, next, random):
self.val = val
self.next = next
self.random = random
def __key(self):
return (self.val, self.next, self.random)
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
if isinstance(other, Node):
return self.__key() == other.__key()
return NotImplemented