Python: Можете ли вы сделать это __eq__ легко понять? - PullRequest
4 голосов
/ 26 ноября 2009

У меня есть еще один вопрос к вам.

У меня есть класс python со списком 'metainfo'. Этот список содержит имена переменных, которые может содержать мой класс . Я написал __eq__ метод, который возвращает True, если оба self и other имеют одинаковые переменные из metainfo, и эти переменные имеют одинаковое значение.

Вот моя реализация:

 def __eq__(self, other):
    for attr in self.metainfo:
      try:
        ours = getattr(self, attr) 
        try:
          theirs = getattr(other, attr)
          if ours != theirs:
            return False
        except AttributeError:
          return False
      except AttributeError:
        try:
          theirs = getattr(other, attr)
          return False
        except AttributeError:
          pass
    return True

У кого-нибудь есть предложения относительно того, как сделать этот код проще для глаз? Будь таким безжалостным, насколько захочешь.

Ответы [ 9 ]

9 голосов
/ 26 ноября 2009

Используйте третий аргумент getattr для установки различных значений по умолчанию:

def __eq__(self, other):
    return all(getattr(self, a, Ellipsis) == getattr(other, a, Ellipsis)
               for a in self.metainfo)

В качестве значения по умолчанию установите то, что никогда не будет фактическим значением, например Ellipsis & dagger; . Таким образом, значения будут совпадать, только если оба объекта содержат одно и то же значение для определенного атрибута или , если оба не имеют указанного атрибута.

Редактировать : как указывает Надя , NotImplemented может быть более подходящей константой (если вы не сохраняете результат богатых сравнений). .).

Редактировать 2: Действительно, как указывает Lac , просто использование hasattr приводит к более удобочитаемому решению:

def __eq__(self, other):
    return all(hasattr(self, a) == hasattr(other, a) and
               getattr(self, a) == getattr(other, a) for a in self.metainfo)

& dagger; : для большей неясности вы можете написать ... вместо Ellipsis, таким образом getattr(self, a, ...) и т. Д. Нет, не делайте этого:)

9 голосов
/ 26 ноября 2009

Я бы добавил строку документации, которая объясняет, с чем она сравнивается, как вы сделали в своем вопросе.

5 голосов
/ 26 ноября 2009
def __eq__(self, other):
    """Returns True if both instances have the same variables from metainfo
    and they have the same values."""
    for attr in self.metainfo:
        if attr in self.__dict__:
            if attr not in other.__dict__:
                return False
            if getattr(self, attr) != getattr(other, attr):
                return False
            continue
        else:
            if attr in other.__dict__:
                return False
    return True
3 голосов
/ 26 ноября 2009

Поскольку это облегчает понимание, не короткое или очень быстрое:

class Test(object):

    def __init__(self):
        self.metainfo = ["foo", "bar"]

    # adding a docstring helps a lot
    # adding a doctest even more : you have an example and a unit test
    # at the same time ! (so I know this snippet works :-))
    def __eq__(self, other):
        """
            This method check instances equality and returns True if both of
            the instances have the same attributs with the same values.
            However, the check is performed only on the attributs whose name
            are listed in self.metainfo.

            E.G :

            >>> t1 = Test()
            >>> t2 = Test()
            >>> print t1 == t2
            True
            >>> t1.foo = True
            >>> print t1 == t2
            False
            >>> t2.foo = True
            >>> t2.bar = 1
            >>> print t1 == t2
            False
            >>> t1.bar = 1
            >>> print t1 == t2
            True
            >>> t1.new_value = "test"
            >>> print t1 == t2
            True
            >>> t1.metainfo.append("new_value")
            >>> print t1 == t2
            False

        """

        # Then, let's keep the code simple. After all, you are just
        # comparing lists :

        self_metainfo_val = [getattr(self, info, Ellipsis)
                             for info in self.metainfo]
        other_metainfo_val = [getattr(other, info, Ellipsis)
                              for info in self.metainfo]
        return self_metainfo_val == other_metainfo_val
3 голосов
/ 26 ноября 2009

Переход с "Flat лучше, чем вложенный" Я бы удалил вложенные операторы try. Вместо этого getattr должен вернуть часового, который равен только самому себе. Однако, в отличие от Stephan202, я предпочитаю держать цикл for. Я бы тоже создал дозорного, а не использовал бы какой-нибудь существующий объект Python. Это гарантирует отсутствие ложных срабатываний даже в самых экзотических ситуациях.

def __eq__(self, other):
    if set(metainfo) != set(other.metainfo):
        # if the meta info differs, then assume the items differ.
        # alternatively, define how differences should be handled
        # (e.g. by using the intersection or the union of both metainfos)
        # and use that to iterate over
        return False
    sentinel = object() # sentinel == sentinel <=> sentinel is sentinel
    for attr in self.metainfo:
        if getattr(self, attr, sentinel) != getattr(other, attr, sentinel):
            return False
    return True

Кроме того, метод должен иметь строку документации, объясняющую его поведение eq ; То же самое относится и к классу, который должен иметь строку документации, объясняющую использование атрибута metainfo.

Наконец, также должен присутствовать юнит-тест для этого поведения равенства. Вот некоторые интересные тестовые примеры:

  1. Объекты, имеющие одинаковое содержимое для всех атрибутов metainfo, но различное содержимое для некоторых других атрибутов (=> они равны)
  2. Если требуется, проверка на коммутативность равна, т. Е. Если a == b: b == a
  3. Объекты, для которых не установлен ни один из атрибутов metainfo
1 голос
/ 26 ноября 2009

Вот вариант, который довольно легко читается IMO, без использования часовых объектов. Сначала он сравнивает, имеет ли атрибут атрибут или нет, затем сравнивает значения.

Это можно сделать в одной строке, используя all () и выражение генератора, как Стивен, но я чувствую, что это более читабельно.

def __eq__(self, other):
    for a in self.metainfo:
        if hasattr(self, a) != hasattr(other, a):
             return False
        if getattr(self, a, None) != getattr(other, a, None):
             return False
    return True
1 голос
/ 26 ноября 2009

Попробовать / исключить затруднит чтение вашего кода. Я бы использовал getattr со значением по умолчанию, которое гарантированно не было бы иначе. В приведенном ниже коде я просто создаю временный объект. Таким образом, если объект не имеет заданного значения, они оба возвращают «NOT_PRESENT» и, таким образом, считаются равными.


def __eq__(self, other):
    NOT_PRESENT = object()
    for attr in self.metainfo:
        ours = getattr(self, attr, NOT_PRESENT) 
        theirs = getattr(other, attr, NOT_PRESENT)
        if ours != theirs:
            return False
    return True
1 голос
/ 26 ноября 2009

Я бы разбил логику на отдельные куски, которые легче понять, каждый из которых проверяет свое условие (и каждый предполагает, что предыдущая вещь была проверена). Проще всего показать код:

# First, check if we have the same list of variables.
my_vars = [var for var in self.metainf if hasattr(self, var)]
other_vars = [var for var in other.metainf if hasattr(other, var)]

if my_vars.sorted() != other_vars.sorted():
  return False # Don't even have the same variables.

# Now, check each variable:
for var in my_vars:
   if self.var != other.var:
      return False # We found a variable with a different value.

# We're here, which means we haven't found any problems!
return True

Редактировать: Я неправильно понял вопрос, вот обновленная версия. Я все еще думаю, что это ясный способ написать такую ​​логику, но она уродливее, чем я предполагал, и совсем не эффективна, поэтому в этом случае я бы, вероятно, выбрал другое решение.

0 голосов
/ 26 ноября 2009

Мне нравится ответ Stephan202, но я думаю, что его код недостаточно проясняет условия равенства. Вот мой взгляд на это:

def __eq__(self, other):
    wehave = [attr for attr in self.metainfo if hasattr(self, attr)]
    theyhave = [attr for attr in self.metainfo if hasattr(other, attr)]
    if wehave != theyhave:
        return False
    return all(getattr(self, attr) == getattr(other, attr) for attr in wehave)
...