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

Bjö*_*lex 46 python inheritance tuples

假设我有namedtuple这样的:

EdgeBase = namedtuple("EdgeBase", "left, right")
Run Code Online (Sandbox Code Playgroud)

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

class Edge(EdgeBase):
    def __hash__(self):
        return hash(self.left) * hash(self.right)
Run Code Online (Sandbox Code Playgroud)

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

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

    def __hash__(self):
        return self._hash
Run Code Online (Sandbox Code Playgroud)

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

hab*_*bit 51

编辑2017年: 结果namedtuple不是一个好主意.attrs是现代的替代品.

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
Run Code Online (Sandbox Code Playgroud)

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

cls必须通过两次到super调用,__new__因为__new__,由于历史/奇怪的原因隐含a staticmethod.

  • 您的解决方案不使用`super`,因此会破坏任何类型的多重继承情况.如果*你*不使用MI并不重要; 如果其他人这样做,他们的代码将会崩溃.通过在任何地方使用`super`来避免这个问题并不困难. (9认同)
  • 将`__slots__ =()`添加到类中也是有益的.否则将创建`__dict__`,否定`namedtuple`的内存效率. (7认同)
  • 为什么我更喜欢这个解决方案(这不是一个愤世嫉俗的问题,我真的想了解是否存在重大差异)? (3认同)
  • 除非并且直到它取代了标准库中的“ namedtuple”,否则“ attrs”不能被视为“现代”替代。不需要第三方依赖性仍然是常见的设计约束。 (3认同)

pyl*_*ang 5

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

代码

假设和int的类型,我们使用默认的 hash via +关键字:leftrightunsafe_hash

import dataclasses as dc


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


hash(Edge(1, 2))
# 3713081631934410656
Run Code Online (Sandbox Code Playgroud)

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

{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)}
Run Code Online (Sandbox Code Playgroud)

细节

我们也可以重写该__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
Run Code Online (Sandbox Code Playgroud)

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

+注意,名称“不安全”表明尽管是可变对象,仍将使用默认哈希。这可能是不受欢迎的,特别是在需要不可变键的字典中。可以使用适当的关键字打开不可变哈希。另请参阅有关数据类中的散列逻辑相关问题的更多信息。