如何为namedtuple的子类提供额外的初始化?

时间:2010-09-02 07:49:30

标签: python inheritance tuples

假设我有namedtuple这样:

EdgeBase = namedtuple("EdgeBase", "left, right")

我想为此实现一个自定义哈希函数,所以我创建了以下子类:

class Edge(EdgeBase):
    def __hash__(self):
        return hash(self.left) * hash(self.right)

由于对象是不可变的,我希望哈希值只计算一次,所以我这样做:

class Edge(EdgeBase):
    def __init__(self, left, right):
        self._hash = hash(self.left) * hash(self.right)

    def __hash__(self):
        return self._hash

这似乎有效,但我真的不确定Python中的子类化和初始化,特别是使用元组。这个解决方案有什么缺陷吗?有推荐的方法怎么做?好吗?提前谢谢。

3 个答案:

答案 0 :(得分:49)

2017年编辑: turns out namedtuple isn't a great ideaattrs是现代的选择。

class Edge(EdgeBase):
    def __new__(cls, left, right):
        self = super(Edge, cls).__new__(cls, left, right)
        self._hash = hash(self.left) * hash(self.right)
        return self

    def __hash__(self):
        return self._hash

__new__是你想在这里调用的,因为元组是不可变的。不可变对象在__new__中创建,然后返回给用户,而不是填充__init__中的数据。

必须将{p> cls两次传递给super上的__new__来电,因为__new__由于历史/奇怪的原因而隐含staticmethod

答案 1 :(得分:3)

问题中的代码可以从__init__中的超级调用中受益,以防它在多重继承情况下被子类化,但是否则是正确的。

class Edge(EdgeBase):
    def __init__(self, left, right):
        super(Edge, self).__init__(left, right)
        self._hash = hash(self.left) * hash(self.right)

    def __hash__(self):
        return self._hash

虽然元组只读取它们的子类的元组部分是只读的,但其他属性可以像往常一样编写,这样就可以将赋值分配给_hash,无论它是在__init__还是__new__中完成的。您可以通过将子类__slots__设置为()来完全只读子类,这具有节省内存的额外好处,但是您将无法分配给_hash。

答案 2 :(得分:0)

在Python 3.7+中,您现在可以使用dataclasses轻松构建可哈希化的类。

代码

假设intleft的类型为right,我们通过unsafe_hash + 关键字使用默认哈希:

import dataclasses as dc


@dc.dataclass(unsafe_hash=True)
class Edge:
    left: int
    right: int


hash(Edge(1, 2))
# 3713081631934410656

现在,我们可以将这些(可变)可哈希对象用作集合中的元素或(字典中的键)。

{Edge(1, 2), Edge(1, 2), Edge(2, 1), Edge(2, 3)}
# {Edge(left=1, right=2), Edge(left=2, right=1), Edge(left=2, right=3)}

详细信息

我们也可以覆盖__hash__函数:

@dc.dataclass
class Edge:
    left: int
    right: int

    def __post_init__(self):
        # Add custom hashing function here
        self._hash = hash((self.left, self.right))         # emulates default

    def __hash__(self):
        return self._hash


hash(Edge(1, 2))
# 3713081631934410656

扩展@ShadowRanger的注释,OP的自定义哈希函数不可靠。特别地,属性值可以互换,例如。 hash(Edge(1, 2)) == hash(Edge(2, 1)),这可能不是预期的。

+ 注意,名称“不安全”表示尽管对象可变,但仍将使用默认哈希。这可能是不希望的,尤其是在期待不可变键的字典中。可以使用适当的关键字打开不可变的哈希。另请参见数据类中的hashing logicrelated issue