【问题标题】:Object of custom type as dictionary key自定义类型的对象作为字典键
【发布时间】:2011-06-21 13:41:04
【问题描述】:

我必须怎么做才能将自定义类型的对象用作 Python 字典中的键(我不希望“对象 id”充当键),例如

class MyThing:
    def __init__(self,name,location,length):
            self.name = name
            self.location = location
            self.length = length

如果名称和位置相同,我想将 MyThing 用作被视为相同的键。 从 C#/Java 开始,我习惯于重写并提供 equals 和 hashcode 方法,并承诺不会改变 hashcode 所依赖的任何内容。

我必须在 Python 中做什么才能完成此任务?我什至应该吗?

(在一个简单的情况下,比如这里,最好只放置一个 (name,location) 元组作为键 - 但考虑我希望键是一个对象)

【问题讨论】:

  • 使用哈希有什么问题?
  • 可能是因为他想要两个MyThing,如果它们具有相同的namelocation,来索引字典以返回相同的值,即使它们是作为两个不同的“对象”。
  • “也许最好只放置一个(名称,位置)元组作为键 - 但考虑我希望键是一个对象)”你的意思是:一个非复合对象?

标签: python dictionary


【解决方案1】:

我知道其他人可能会像我一样在这里结束,今天的答案是在 python >3.7 中使用数据类。它同时具有 hasheq 函数。

【讨论】:

  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
【解决方案2】:

我注意到在 python 3.8.8(可能更早)中,您不再需要明确声明 __eq__()__hash__() 就必须有机会使用自己的类作为 dict 中的键。

class Apple:
    def __init__(self, weight):
        self.weight = weight
        
    def __repr__(self):
        return f'Apple({self.weight})'

apple_a = Apple(1)
apple_b = Apple(1)
apple_c = Apple(2)

apple_dictionary = {apple_a : 3, apple_b : 4, apple_c : 5}

print(apple_dictionary[apple_a])  # 3
print(apple_dictionary)  # {Apple(1): 3, Apple(1): 4, Apple(2): 5}

我假设有一段时间 Python 会自行管理它,但我可能错了。

【讨论】:

  • 我发现这也是真的。
【解决方案3】:

需要添加2 methods,注意__hash____eq__

class MyThing:
    def __init__(self,name,location,length):
        self.name = name
        self.location = location
        self.length = length

    def __hash__(self):
        return hash((self.name, self.location))

    def __eq__(self, other):
        return (self.name, self.location) == (other.name, other.location)

    def __ne__(self, other):
        # Not strictly necessary, but to avoid having both x==y and x!=y
        # True at the same time
        return not(self == other)

Python dict documentation 定义了对关键对象的这些要求,即它们必须是 hashable

【讨论】:

  • hash(self.name) 看起来比self.name.__hash__() 更好,如果你这样做了,你可以使用hash((x, y)) 来避免自己异或。
  • 作为补充说明,我刚刚发现像这样调用x.__hash__() 也是错误,因为它可以产生不正确 结果:pastebin.com/C9fSH7eF
  • @Rosh Oxymoron:感谢您的评论。写作时,我使用显式的and 表示__eq__,但后来我想“为什么不使用元组?”因为无论如何我经常这样做(我认为它更具可读性)。然而,出于某种奇怪的原因,我的眼睛并没有回过头来质疑__hash__
  • @user877329:您是否尝试使用某些搅拌机数据结构作为键?显然,从某些存储库中,某些对象要求您首先“冻结”它们以避免可变性(不允许更改已用作 python 字典中的键的基于值的对象)
  • @kawing-chiu pythonfiddle.com/eq-method-needs-ne-method Python 3 没有这个问题:默认的 __ne__() 已经"fixed".
【解决方案4】:

在 Python 2.6 或更高版本中的替代方法是使用 collections.namedtuple() -- 它可以节省您编写任何特殊方法的时间:

from collections import namedtuple
MyThingBase = namedtuple("MyThingBase", ["name", "location"])
class MyThing(MyThingBase):
    def __new__(cls, name, location, length):
        obj = MyThingBase.__new__(cls, name, location)
        obj.length = length
        return obj

a = MyThing("a", "here", 10)
b = MyThing("a", "here", 20)
c = MyThing("c", "there", 10)
a == b
# True
hash(a) == hash(b)
# True
a == c
# False

【讨论】:

    【解决方案5】:

    如果你想要特殊的哈希语义,你覆盖 __hash__,并覆盖 __cmp____eq__ 以使你的类可用作键。比较相等的对象需要具有相同的哈希值。

    Python 期望 __hash__ 返回一个整数,不推荐返回 Banana() :)

    用户定义的类默认有__hash__,它调用id(self),正如你所提到的。

    documentation 提供了一些额外的提示:

    继承 __hash__() 的类 来自父类的方法,但改变 __cmp__()__eq__() 的含义 这样返回的哈希值为 不再合适(例如,通过 转向基于价值的概念 平等而不是默认 基于身份的平等)可以 明确地将自己标记为 通过设置 __hash__ = None 不可散列 在类定义中。这样做 意味着不仅将实例 班级提出适当的 TypeError 当程序尝试 检索他们的哈希值,但他们 也将被正确识别为 检查时不可散列 isinstance(obj, collections.Hashable) (与定义自己的类不同 __hash__() 显式引发 TypeError)。

    【讨论】:

    • 仅哈希是不够的,另外你需要覆盖__eq____cmp__
    • @Oben Sonne:__cmp__ 如果它是用户定义的类,则由 Python 提供给您,但您可能还是希望覆盖它们以适应新的语义。
    • @Skurmedel:是的,但是尽管您可以调用cmp 并在不覆盖这些方法的用户类上使用=,但必须实现其中之一以满足提问者的要求,即具有相似的名称和位置具有相同的字典键。
    猜你喜欢
    • 2011-10-23
    • 1970-01-01
    • 2011-08-16
    • 2012-12-31
    • 2018-02-16
    • 2019-09-13
    • 1970-01-01
    • 2011-01-10
    相关资源
    最近更新 更多