Питон:Можете ли вы сделать этот __eq__ простым для понимания?

StackOverflow https://stackoverflow.com/questions/1803710

  •  05-07-2019
  •  | 
  •  

Вопрос

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

У меня есть класс Python со списком «метаинформация».Этот список содержит имена переменных, которые мой класс мощь содержать.Я написал __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

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

Это было полезно?

Решение

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

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

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

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

Редактировать 2: Действительно, как Лак указывает, просто используя 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)

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

Другие советы

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

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

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

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

Переход с "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.

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

<Ол>
  • Объекты, имеющие одинаковое содержимое для всех атрибутов metainfo, но различное содержимое для некоторых других атрибутов (= > они равны)
  • Если требуется, проверка на коммутативность равных, т. е. если a == b: b == a
  • Объекты, для которых не установлен ни один из атрибутов metainfo
  • Я бы разбил логику на отдельные фрагменты, которые легче понять, каждый из которых проверяет свое условие (и каждый предполагает, что предыдущее было проверено).Проще всего показать код:

    # 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
    

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

    Попытки / исключения делают ваш код сложнее для чтения. Я бы использовал 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
    

    Вот вариант, который довольно легко читается 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
    

    Мне нравится ответ 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)
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top