4

我还有一个问题要问你。

我有一个带有“元信息”列表的 python 类。此列表包含我的班级可能包含的变量名称。我编写了一个__eq__方法,如果两者具有相同的变量,并且这些变量具有相同的值self,则返回 True。othermetainfo

这是我的实现:

 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

有没有人对我如何使这段代码更容易看到有任何建议?随心所欲地无情。

4

9 回答 9

9

正如您在问题中所做的那样,我将添加一个解释其比较内容的文档字符串。

于 2009-11-26T13:42:49.070 回答
9

使用getattr的第三个参数设置不同的默认值:

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

作为默认值,设置永远不会是实际值的东西,例如。因此,只有当两个对象都包含某个属性的相同值两者都没有所述属性时,这些值才会匹配。Ellipsis

编辑:正如Nadia指出的那样,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)

  :为了额外的晦涩,你可以写...而不是Ellipsis,因此getattr(self, a, ...)等等。不,不要这样做:)

于 2009-11-26T13:45:52.230 回答
5
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
于 2009-11-26T13:46:29.953 回答
3

使用“Flat is better than nested”我会删除嵌套的 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行为;对于应该有一个解释元信息属性使用的文档字符串的类也是如此。

最后,还应该存在针对这种平等行为的单元测试。一些有趣的测试用例是:

  1. 对于所有元信息属性具有相同内容但对于某些其他属性具有不同内容的对象(=> 它们是相等的)
  2. 如果需要,检查等式的交换性,即如果 a == b: b == a
  3. 没有任何元信息属性集的对象
于 2009-11-26T14:15:03.547 回答
3

由于它将使其易于理解,而不是简短或非常快:

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
于 2009-11-26T14:16:35.913 回答
1

我会将逻辑分解成更易于理解的单独块,每个块检查不同的条件(并且每个都假设检查了前一个内容)。最简单的只是显示代码:

# 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

编辑:我误解了这个问题,这是一个更新版本。我仍然认为这是编写这种逻辑的一种清晰方法,但它比我预期的更丑陋,而且根本没有效率,所以在这种情况下,我可能会采用不同的解决方案。

于 2009-11-26T13:45:06.587 回答
1

try/excepts 使您的代码更难阅读。我会使用带有默认值的 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
于 2009-11-26T14:09:16.960 回答
1

这是一个非常容易阅读 IMO 的变体,无需使用哨兵对象。它将首先比较两者是否具有属性,然后比较值。

可以像 Stephen 一样使用 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
于 2009-11-26T14:16:58.770 回答
0

我喜欢 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)
于 2009-11-26T14:16:38.457 回答