Python:覆盖__eq__和__hash__的这种方式是否可行?

24

我是Python的新手,我想确保正确重写了__eq____hash__,以免以后出现痛苦的错误:

(我正在使用Google App Engine。)

class Course(db.Model):
    dept_code = db.StringProperty()
    number = db.IntegerProperty()
    title = db.StringProperty()
    raw_pre_reqs = db.StringProperty(multiline=True)
    original_description = db.StringProperty()

    def getPreReqs(self):
        return pickle.loads(str(self.raw_pre_reqs))

    def __repr__(self):
        title_msg = self.title if self.title else "Untitled"
        return "%s %s: %s" % (self.dept_code, self.number, title_msg)

    def __attrs(self):
        return (self.dept_code, self.number, self.title, self.raw_pre_reqs, self.original_description)

    def __eq__(self, other):
        return isinstance(other, Course) and self.__attrs() == other.__attrs()

    def __hash__(self):
        return hash(self.__attrs())

稍微复杂一些的类型:

class DependencyArcTail(db.Model):
    ''' A list of courses that is a pre-req for something else '''
    courses = db.ListProperty(db.Key)

    ''' a list of heads that reference this one '''
    forwardLinks = db.ListProperty(db.Key)

    def __repr__(self):
        return "DepArcTail %d: courses='%s' forwardLinks='%s'" % (id(self), getReprOfKeys(self.courses), getIdOfKeys(self.forwardLinks))

    def __eq__(self, other):
        if not isinstance(other, DependencyArcTail):
            return False

        for this_course in self.courses:
            if not (this_course in other.courses):
                return False

        for other_course in other.courses:
            if not (other_course in self.courses):
                return False

        return True

    def __hash__(self):
        return hash((tuple(self.courses), tuple(self.forwardLinks)))

一切看起来都好吗?

更新以反映@Alex的评论

class DependencyArcTail(db.Model):
    ''' A list of courses that is a pre-req for something else '''
    courses = db.ListProperty(db.Key)

    ''' a list of heads that reference this one '''
    forwardLinks = db.ListProperty(db.Key)

    def __repr__(self):
        return "DepArcTail %d: courses='%s' forwardLinks='%s'" % (id(self), getReprOfKeys(self.courses), getIdOfKeys(self.forwardLinks))

    def __eq__(self, other):
        return isinstance(other, DependencyArcTail) and set(self.courses) == set(other.courses) and set(self.forwardLinks) == set(other.forwardLinks)

    def __hash__(self):
        return hash((tuple(self.courses), tuple(self.forwardLinks)))
1个回答

18
第一个没问题。第二个有两个问题:
1. .courses 可能会有重复项。 2. 具有相同 .courses 但不同 .forwardLinks 的两个实体将比较相等但具有不同的哈希值。
我会通过使相等性取决于课程和转发链接来解决第二个问题,但是对于集合的更改(因此没有重复项),哈希也是相同的。即:
```python class Entity: def __init__(self, courses, forwardLinks): self.courses = set(courses) self.forwardLinks = set(forwardLinks)
def __eq__(self, other): return self.courses == other.courses and self.forwardLinks == other.forwardLinks
def __hash__(self): return hash(frozenset(self.courses)) ^ hash(frozenset(self.forwardLinks)) ```
def __eq__(self, other):
    if not isinstance(other, DependencyArcTail):
        return False

    return (set(self.courses) == set(other.courses) and
            set(self.forwardLinks) == set(other.forwardLinks))

def __hash__(self):
    return hash((frozenset(self.courses), frozenset(self.forwardLinks)))

当然,这是基于前向链接对于对象的“真实价值”至关重要的假设,否则它们应该从__eq____hash__中省略。

编辑:从__hash__中删除对tuple的调用,这在最好的情况下是多余的(并可能会造成损害,正如@Mark的评论所建议的那样[[tx!]]);在哈希过程中将set更改为frozenset,正如@Phillips的评论所建议的那样[[tx!]]。


1
@Alex:这个哈希值不是依赖于 tuple(set(self.courses)) 中元素的顺序吗?而这个顺序可能有些随意? - Mark Dickinson
@Mark,这是任意的但不是反复无常的——虽然我不能100%确定具有相同项目的任意洗牌列表会产生相同排序的集合(它可能会随着Python版本的变化而异),因此最好避免我懒惰地留在元组调用中——让我相应地进行编辑,谢谢。 - Alex Martelli
4
我认为你应该使用 frozenset 而不是 set,后者是不可哈希的。 - Philipp
当你重写等于方法时,不应该建议同时重写不等于方法以保持一致的行为吗?https://dev59.com/H3RC5IYBdhLWcg3wKtz2? - Alex Punnen
这是一个不完整的答案。请在这里查看。几点建议:1. 您应该实现__ne__(并且可能使用total_ordering)。2. 当您不知道如何相等时,应返回NotImplemented - Guy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接