Python: Это хороший способ переопределения __eq__ и __hash__? - PullRequest
19 голосов
/ 19 июня 2010

Я новичок в Python, и я хотел убедиться, что я корректно переопределил __eq__ и __hash__, чтобы позже не вызывать болезненных ошибок:

(я использую Google AppEngine.)

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 Ответ

14 голосов
/ 19 июня 2010

Первый в порядке. Второй проблемный по двум причинам:

  1. могут быть дубликаты в .courses
  2. две сущности с одинаковыми .courses, но разными .forwardLinks будут сравниваться равными, но иметь разные хэши

Я бы исправил второй, сделав равенство зависимым как от курсов, так и от прямых связей, но от обоих изменений в наборах (следовательно, без дубликатов), и то же самое для хеширования. I.e.:

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 !!!]].

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...