代码之家  ›  专栏  ›  技术社区  ›  Björn Pollex

如何为NamedDuple的子类提供附加初始化?

  •  44
  • Björn Pollex  · 技术社区  · 14 年前

    假设我有一个 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 回复  |  直到 6 年前
        1
  •  49
  •   habnabit dwc    7 年前

    编辑2017: turns out namedtuple isn't a great idea . 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
    

    __new__ 因为元组是不可变的,所以要在这里调用它。不可变对象创建于 新西兰 然后返回给用户,而不是在 __init__ .

    cls 必须通过两次 super 拜访 新西兰 因为 新西兰 出于历史/奇怪的原因,隐式地 staticmethod .

        2
  •  3
  •   ShadowRanger    6 年前

    问题中的代码可以从 __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
    

    虽然元组是只读的,但是它们的子类的元组部分是只读的,但是其他属性可以像往常一样写入,这允许将赋值散列,而不管它是否在 爱因斯坦 __new__ . 通过设置子类的 __slots__ 到(),这有节省内存的额外好处,但是您将无法分配给_hash。

        3
  •  0
  •   pylang    6 年前

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

    代码

    假设 int 类型 left right ,我们使用默认的散列方式via unsafe_hash + 关键词:

    import dataclasses as dc
    
    
    @dc.dataclass(unsafe_hash=True)
    class Edge:
        left: int
        right: int
    
    
    hash(Edge(1, 2))
    # 3713081631934410656
    

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

    {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”表示尽管对象是可变的,但仍将使用默认哈希。这可能是不需要的,特别是在dict中需要不可变的键。不可变散列可以用适当的关键字打开。另请参见更多内容 hashing logic 在数据类和 related issue .