Pregunta

Tengo otra pregunta para ti.

Tengo una clase de python con una lista 'metainfo'. Esta lista contiene nombres de variables que mi clase podría contener. Escribí un método __eq__ que devuelve True si tanto self como other tienen las mismas variables de metainfo y esas las variables tienen el mismo valor.

Aquí está mi implementación:

 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

¿Alguien tiene alguna sugerencia sobre cómo puedo hacer que este código sea más fácil a la vista? Sé tan despiadado como quieras.

¿Fue útil?

Solución

Utilice getattr 's Tercer argumento para establecer distintos valores por defecto:

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

Como valor predeterminado, establezca algo que nunca será un valor real, como Ellipsis . Por lo tanto, los valores solo coincidirán si ambos objetos contienen el mismo valor para un determinado atributo o si ambos no tienen dicho atributo.

Editar : como Nadia señala, NotImplemented puede ser una constante más apropiada (a menos que esté almacenando el resultado de ricas comparaciones ...).

Edición 2: De hecho, como lo señala Lac , simplemente use hasattr da como resultado una solución más legible:

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

& nbsp; : para mayor claridad, puede escribir ... en lugar de Ellipsis , por lo tanto, getattr (self, a, ...) etc. No, no lo hagas :)

Otros consejos

Agregaría una cadena de documentos que explique lo que compara, como hiciste en tu pregunta.

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

Ya que está a punto de facilitar su comprensión, no es corto ni muy 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

Ir con " Plano es mejor que anidado " Quitaría las declaraciones de prueba anidadas. En su lugar, getattr debería devolver un centinela que solo sea igual a sí mismo. A diferencia de Stephan202, sin embargo, prefiero mantener el bucle for. También crearía un centinela por mí mismo y no reutilizar algún objeto de Python existente. Esto garantiza que no hay falsos positivos, incluso en las situaciones más exóticas.

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

Además, el método debe tener una cadena de documentos que explique su comportamiento eq ; Lo mismo ocurre con la clase que debe tener una cadena de documentos que explique el uso del atributo metainfo.

Finalmente, una prueba de unidad para este comportamiento de igualdad también debería estar presente. Algunos casos de prueba interesantes serían:

  1. Objetos que tienen el mismo contenido para todos los atributos de metainfo, pero contenido diferente para algunos otros atributos (= > son iguales)
  2. Si es necesario, verifique la conmutatividad de iguales, es decir, si a == b: b == a
  3. Objetos que no tienen ninguno de los atributos de metainfo establecidos

Yo dividiría la lógica en partes separadas que sean más fáciles de entender, cada una verificando una condición diferente (y cada una asumiendo que se verificó lo anterior). Más fácil solo para mostrar el 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: no entendí la pregunta, aquí hay una versión actualizada. Sigo pensando que esta es una forma clara de escribir este tipo de lógica, pero es más fea de lo que pretendía y no es del todo eficiente, por lo que en este caso probablemente vaya con una solución diferente.

El try / excepts hace que tu código sea más difícil de leer. Usaría getattr con un valor predeterminado que se garantiza que no estará allí de otra manera. En el código de abajo acabo de hacer un objeto temporal. De esa manera, si el objeto no tiene un valor dado, ambos devolverán " NOT_PRESENT " y así contar como ser igual.


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

Aquí hay una variante que es bastante fácil de leer IMO, sin usar objetos centinela. Primero comparará si ambos tienen o no tiene el atributo, luego comparen los valores.

Se podría hacer en una línea usando all () y una expresión generadora como lo hizo Stephen, pero creo que esto es más legible.

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

Me gusta la respuesta de Stephan202, pero creo que su código no hace que las condiciones de igualdad sean lo suficientemente claras. Aquí está mi opinión sobre él:

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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top