Question

J'ai une autre question à vous poser.

J'ai une classe python avec une liste 'metainfo'. Cette liste contient les noms de variables que ma classe pourrait contenir. J'ai écrit une méthode __ eq __ qui renvoie True si le self et le autre ont les mêmes variables que metainfo et ceux les variables ont la même valeur.

Voici ma mise en œuvre:

 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

Quelqu'un a-t-il des suggestions sur la manière de rendre ce code plus facile à regarder? Soyez aussi impitoyable que vous le souhaitez.

Était-ce utile?

La solution

Utilisez les getattr . troisième argument pour définir des valeurs par défaut distinctes:

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

Comme valeur par défaut, définissez quelque chose qui ne sera jamais une valeur réelle, tel que Ellipsis & # 8224; . Ainsi, les valeurs ne correspondront que si les deux objets contiennent la même valeur pour un certain attribut ou si les deux n’ont pas cet attribut.

Modifier : comme Nadia , NotImplemented peut être une constante plus appropriée (sauf si vous enregistrez le résultat de comparaisons riches ...).

Modifier 2: En effet, comme le Lac le souligne, il suffit d'utiliser hasattr a pour résultat une solution plus lisible:

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; & # 8224; : pour plus de clarté, vous pouvez écrire ... au lieu de Ellipsis , ainsi getattr (self, a,. ..) etc. Non, ne le faites pas:)

Autres conseils

J'ajouterais une docstring qui explique ce qu'elle compare, comme vous l'avez fait dans votre question.

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

Puisqu'il est sur le point de le rendre facile à comprendre, ni court ni très rapide:

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

Utiliser "Flat est préférable à imbriqué" , je supprimerais les instructions try imbriquées. Au lieu de cela, getattr devrait renvoyer une sentinelle qui n’est égale qu’à elle-même. Contrairement à Stephan202, cependant, je préfère garder la boucle for. Je créerais moi-même une sentinelle et ne réutiliserais pas un objet Python existant. Cela garantit qu'il n'y a pas de faux positifs, même dans les situations les plus exotiques.

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

De plus, la méthode devrait avoir une chaîne de documentation expliquant son comportement eq ; Il en va de même pour la classe qui devrait avoir une docstring expliquant l’utilisation de l’attribut metainfo.

Enfin, un test unitaire pour ce comportement d'égalité devrait également être présent. Voici quelques cas de test intéressants:

  1. Les objets qui ont le même contenu pour tous les attributs metainfo, mais un contenu différent pour certains autres attributs (= ils sont égaux)
  2. Si nécessaire, vérification de la commutativité d'égaux, c'est-à-dire si a == b: b == a
  3. Objets pour lesquels aucun des attributs metainfo n'est défini

Je diviserais la logique en plusieurs parties plus faciles à comprendre, chacune vérifiant une condition différente (et chacune supposant que la chose précédente avait été vérifiée). Plus facile juste pour montrer le code:

# 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

Modifier: J'ai mal compris la question. Voici une version mise à jour. Je pense toujours que c’est une façon claire d’écrire ce genre de logique, mais c’est plus laid que je ne le pensais et pas du tout efficace, alors dans ce cas, je choisirais probablement une solution différente.

Les tentatives / exceptions rendent votre code plus difficile à lire. J'utiliserais getattr avec une valeur par défaut garantie de ne pas l'être autrement. Dans le code ci-dessous, je crée juste un objet temp. Ainsi, si l'objet n'a pas de valeur donnée, il retournera tous les deux " NOT_PRESENT " et compter ainsi comme étant égal.


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

Voici une variante assez facile à lire à l’OMI, sans utiliser d’objets sentinelles. Il comparera d'abord si les deux ont ou n'a pas l'attribut, puis comparera les valeurs.

Cela pourrait être fait en une seule ligne en utilisant all () et une expression génératrice comme Stephen, mais j'estime que cela est plus lisible.

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

J'aime la réponse de Stephan202, mais je pense que son code ne précise pas suffisamment les conditions d'égalité. Voici ce que je pense:

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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top