Domanda

Ho un'altra domanda per te.

Ho una classe Python con un elenco "metainfo". Questo elenco contiene i nomi delle variabili che la mia classe potrebbe contenere. Ho scritto un metodo __eq__ che restituisce True se sia self che altro hanno le stesse variabili da metainfo e quelli le variabili hanno lo stesso valore.

Ecco la mia implementazione:

 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

Qualcuno ha qualche suggerimento su come posso rendere questo codice più semplice alla vista? Sii spietato quanto ti pare.

È stato utile?

Soluzione

Usa getattr terzo argomento per impostare valori predefiniti distinti:

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

Come valore predefinito, imposta qualcosa che non sarà mai un valore reale, come Ellissi & # 8224; . Pertanto i valori corrisponderanno solo se entrambi gli oggetti contengono lo stesso valore per un determinato attributo o se entrambi non hanno detto attributo.

Modifica : come Nadia sottolinea, NotImplemented potrebbe essere una costante più appropriata (a meno che non si stia memorizzando il risultato di ricchi confronti ...).

Modifica 2: In effetti, come sottolineato da Lac , usando solo hasattr si traduce in una soluzione più leggibile:

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; : per ulteriore oscurità potresti scrivere ... invece di Ellipsis , quindi getattr (self, a,. ..) ecc. No, non farlo :)

Altri suggerimenti

Aggiungerei un docstring che spiega cosa confronta, come hai fatto nella tua domanda.

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

Dal momento che sta per renderlo facile da capire, non breve o molto veloce:

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

Andando con " Flat è meglio di nidificato " rimuoverei le istruzioni di prova nidificate. Invece, getattr dovrebbe restituire una sentinella che eguaglia solo se stessa. A differenza di Stephan202, tuttavia, preferisco mantenere il ciclo for. Vorrei anche creare una sentinella da solo e non riutilizzare alcuni oggetti Python esistenti. Ciò garantisce che non vi siano falsi positivi, anche nelle situazioni più esotiche.

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

Inoltre, il metodo dovrebbe avere una stringa doc che spieghi il suo comportamento eq ; lo stesso vale per la classe che dovrebbe avere un docstring che spiega l'uso dell'attributo metainfo.

Infine, dovrebbe essere presente anche un unit test per questo comportamento di uguaglianza. Alcuni casi di test interessanti sarebbero:

  1. Oggetti che hanno lo stesso contenuto per tutti gli attributi metainfo, ma contenuto diverso per alcuni altri attributi (= > sono uguali)
  2. Se richiesto, verifica la commutatività degli uguali, ovvero se a == b: b == a
  3. Oggetti che non hanno nessuno degli attributi metainfo impostati

Vorrei spezzare la logica in blocchi separati che sono più facili da capire, ognuno controllando una condizione diversa (e ognuno assumendo che la cosa precedente fosse verificata). Più semplice solo per mostrare il codice:

# 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

Modifica: ho frainteso la domanda, ecco una versione aggiornata. Continuo a pensare che sia un modo chiaro per scrivere questo tipo di logica, ma è più brutto di quanto pensassi e per nulla efficiente, quindi in questo caso probabilmente sceglierei una soluzione diversa.

I tentativi / eccezioni rendono il codice più difficile da leggere. Userei getattr con un valore predefinito che è garantito per non essere altrimenti lì. Nel codice qui sotto ho appena creato un oggetto temp. In questo modo se l'oggetto non ha un dato valore, entrambi restituiranno " NOT_PRESENT " e quindi contano come uguali.


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

Ecco una variante che è abbastanza facile da leggere IMO, senza usare oggetti sentinella. Prima confronterà se entrambi hanno o non hanno l'attributo, quindi confrontano i valori.

Potrebbe essere fatto in una riga usando all () e un'espressione del generatore come ha fatto Stephen, ma penso che questo sia più leggibile.

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

Mi piace la risposta di Stephan202, ma penso che il suo codice non chiarisca abbastanza le condizioni di uguaglianza. Ecco la mia opinione su di esso:

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)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top