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

问题描述 投票:44回答:3

假设我有这样的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中的子类化和初始化,特别是对于元组。这个解决方案有什么缺陷吗?有推荐的方法怎么做?好吗?提前致谢。

python inheritance tuples
3个回答
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__中填充数据。

cls必须两次通过对super__new__呼叫,因为__new__由于历史/奇怪的原因隐含地是staticmethod


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。


0
投票

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

假设int类型的leftright,我们使用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)),这可能不是预期的。

+注意,名称“unsafe”表示尽管对象是可变的,但仍将使用默认哈希。这可能是不希望的,特别是在期望不可变密钥的字典中。可以使用适当的关键字打开不可变哈希值。另请参阅有关数据类和hashing logic中的related issue的更多信息。

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