Питон:Можете ли вы сделать этот __eq__ простым для понимания?
Вопрос
У меня есть к вам еще один вопрос.
У меня есть класс 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.
Наконец, также должен присутствовать юнит-тест для этого поведения равенства. Вот некоторые интересные тестовые примеры:
<Ол>Я бы разбил логику на отдельные фрагменты, которые легче понять, каждый из которых проверяет свое условие (и каждый предполагает, что предыдущее было проверено).Проще всего показать код:
# 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)