Pergunta

Eu tenho uma outra pergunta para você.

Eu tenho uma classe python com uma lista 'metainfo'. Esta lista contém nomes de variáveis ??que minha classe força contêm. Eu escrevi um método __eq__ que retorna True se a ambos self e other têm as mesmas variáveis ??de metainfo e essas variáveis ??têm o mesmo valor.

Aqui está minha implementação:

 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

Alguém tem alguma sugestão de como eu posso fazer este código mais fácil no olho? Seja tão implacável quanto quiser.

Foi útil?

Solução

Use getattr 's terceiro argumento para definir valores padrão distintas :

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

Como o valor padrão, defina algo que nunca será um valor real, como Ellipsis . Assim, os valores irão corresponder somente se ambos os objetos contêm o mesmo valor para um determinado atributo ou , se ambos não disseram atributo.

Editar : como Nadia aponta, NotImplemented pode ser uma constante mais apropriado (a menos que você está armazenando o resultado de comparações ricos ... ).

Editar 2: Na verdade, como Lac aponta, apenas usando hasattr resulta em uma solução mais legível:

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

: para a obscuridade extra que você poderia escrever ... vez de Ellipsis, assim getattr(self, a, ...) etc. Não, não faça isso:)

Outras dicas

Eu gostaria de acrescentar uma docstring que explica o que compara, como você fez na sua pergunta.

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

Desde que está prestes a tornar mais fácil de entender, não a curto ou muito rápido:

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

Indo com "Flat é melhor que nested" eu iria remover as instruções try aninhadas. Em vez disso, getattr deve retornar uma sentinela que só é igual a si mesmo. Ao contrário Stephan202, no entanto, eu prefiro manter o loop for. Eu também iria criar uma sentinela por mim, e não re-utilizar algum objeto Python existente. Isso garante que não há falsos positivos, mesmo nas situações mais exóticos.

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

Além disso, o método deve ter um doc-string explicá-lo de eq comportamento; mesmo vale para a classe que deve ter uma docstring explicando o uso do atributo metainfo.

Finalmente, um teste de unidade para essa igualdade-comportamento deve estar presente também. Alguns casos de teste interessante seria:

  1. Os objetos que têm o mesmo conteúdo para todos os metainfo-atributos, mas conteúdo diferente para alguns outros atributos (=> eles são iguais)
  2. Se necessário, a verificação de comutatividade de iguais, ou seja, se a == b: b == um
  3. Os objetos que não têm qualquer um dos metainfo-conjunto de atributos

eu iria quebrar a lógica em pedaços separados que são mais fáceis de compreender, cada um verificando uma condição diferente (e cada um assumindo o anterior foi marcada). Mais fácil apenas para mostrar o código:

# 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

Editar: eu não entendi a pergunta, aqui está uma versão atualizada. Eu ainda acho que essa é uma forma clara de escrever este tipo de lógica, mas é mais feio do que eu pretendia e não em todos eficiente, portanto, neste caso, eu provavelmente vá com uma solução diferente.

Os try / excepts tornar seu código mais difícil de ler. Eu usaria getattr com um valor padrão que é garantido que não de outra forma estar lá. No código abaixo eu só fazer um objeto temporário. Dessa forma, se o objeto não tem um determinado valor que vai tanto retorno "NOT_PRESENT" e, portanto, contam como sendo iguais.


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

Aqui está uma variante que é muito fácil de ler IMO, sem o uso de objetos sentinela. Ele primeiro comparar se ambos tem ou has not o atributo, e depois comparar os valores.

Pode ser feito em uma linha usando tudo () e um gerador de expressão como Stephen fiz, mas eu sinto que este é mais legível.

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

Eu gosto de resposta de Stephan202, mas acho que seu código não tornar as condições de igualdade bastante clara. Aqui está a minha opinião sobre ele:

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)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top