La mejor manera de encontrar objetos que no están presentes en ambas listas

Estoy trabajando en un módulo que depende de verificar si hay algún objeto que no esté presente en ninguna de las 2 listas. Se supone que la implementación está en Python.

Considere la definición de objeto simplificado:

class Foo(object): def __init__(self, attr_one=None, attr_two=None): self.attr_one = attr_one self.attr_two = attr_two def __eq__(self, other): return self.attr_one == other.attr_one and self.attr_two == other.attr_two 

Tengo dos listas separadas que pueden encapsular varias instancias de la clase Foo de la siguiente manera:

 list1 = [Foo('abc', 2), Foo('bcd', 3), Foo('cde', 4)] list2 = [Foo('abc', 2), Foo('bcd', 4), Foo('efg', 5)] 

Necesito averiguar los objetos que están presentes en una lista y ausentes en la otra sobre la base de attr_one. En este caso, la salida deseada para los elementos presentes en la primera lista y que faltan en la segunda lista se muestra a continuación.

 `['Foo('bcd', 3), Foo('cde', 4)]` 

Del mismo modo, los elementos presentes en la lista 2, pero no en la lista 1

  [Foo('bcd', 4), Foo('efg', 5)] 

Me gustaría saber si hay una manera de emparejar la base de attr_one también.

  List 1 List 2 Foo('bcd', 3) Foo('bcd', 4) Foo('cde', 4) None None Foo('efg', 5) 

Como ya tiene definido un método __eq__ , puede usar la comprensión de lista para encontrar la singularidad de los objetos en cualquiera de las listas.

 print [obj for obj in list1 if obj not in list2] 

Una buena manera de comparar listas rápidamente para determinar qué elementos están presentes en uno pero no en el otro es crear conjuntos a partir de las listas originales y tomar la diferencia entre los dos conjuntos. Para que la lista se convierta en un conjunto, los objetos que contiene deben ser hashable , por lo que debe definir un nuevo __hash__() para sus objetos Foo :

 def __hash__(self): return hash((self.attr_one,self.attr_two)) 

Tenga en cuenta que, dado que las tuplas son hashable, siempre que attr_one y attr_two sean tipos hashable, esta implementación debería ser bastante sólida.

Ahora, para determinar qué elementos están presentes en una lista pero no en la otra:

 set1 = set(list1) set2 = set(list2) missing_from_1 = set2 - set1 missing_from_2 = set1 - set2 

Para hacer esto en base a solo uno de los atributos, puede crear sus conjuntos utilizando solo los atributos:

 set1 = set([i.attr_one for i in list1]) 

Por supuesto, esto significa que terminarás con resultados que solo te dirán los valores attr_one que están presentes en una lista pero no en la otra, en lugar de darte los objetos reales de Foo . Los objetos en sí son fáciles de encontrar, sin embargo, una vez que tenga los conjuntos “faltantes”:

 missing_Foos = set() for attr in missing_from_2: for i in list1: if i.attr_one == attr: missing_Foos.add(i) 

Sin embargo, esto puede ser bastante costoso computacionalmente, si tiene listas muy largas.

EDITAR: usar conjuntos solo es realmente útil si tiene listas extremadamente grandes y, por lo tanto, necesita aprovechar la eficiencia computacional de las operaciones de conjuntos. De lo contrario, puede ser más simple usar listas de comprensión, como se sugiere en la otra respuesta.

Hay dos formas de hacerlo: usando sets o con filter :

 class Foo(object): def __init__(self, attr_one=None, attr_two=None): self.attr_one = attr_one self.attr_two = attr_two def __eq__(self, other): return self.attr_one == other.attr_one and self.attr_two == other.attr_two def __hash__(self): return hash(self.attr_one) def __repr__(self): return "".format(self.attr_one, self.attr_two) def main(): a = Foo('test', 1) b = Foo('test', 1) list1 = [Foo('abc', 2), Foo('bcd', 3), Foo('cde', 4)] list2 = [Foo('abc', 2), Foo('bcd', 4), Foo('efg', 5)] # With sets list1set = set(list1) list2set = set(list2) print list1set.intersection(list2set) # Returns set([]) # With filter list2attr_one = [l.attr_one for l in list2] print filter(lambda x: x.attr_one in list2attr_one, list1) # Returns [, ]