Encuentre un objeto en la lista que tenga un atributo igual a algún valor (que cumpla con cualquier condición)

221

Tengo una lista de objetos. Quiero encontrar un objeto (primero o lo que sea) en esta lista que tenga un atributo (o resultado del método, lo que sea) igual a value.

¿Cuál es la mejor manera de encontrarlo?

Aquí está el caso de prueba:

  class Test:
      def __init__(self, value):
          self.value = value

  import random

  value = 5

  test_list = [Test(random.randint(0,100)) for x in range(1000)]

  # that I would do in Pascal, I don't believe isn't anywhere near 'Pythonic'
  for x in test_list:
      if x.value == value:
          print "i found it!"
          break

Creo que usar generadores y reduce()no hará ninguna diferencia porque todavía estaría iterando a través de la lista.

ps .: La ecuación de valuees solo un ejemplo. Por supuesto, queremos obtener un elemento que cumpla con cualquier condición.

seler
fuente
2
Aquí hay una buena discusión sobre esta pregunta: tomayko.com/writings/cleanest-python-find-in-list-function
Andrew Hare
La publicación original está ridículamente desactualizada, pero la segunda respuesta coincide exactamente con mi versión de una línea. Sin embargo, no estoy convencido de que sea mejor que la versión básica de bucle.
agf

Respuestas:

433
next((x for x in test_list if x.value == value), None)

Esto obtiene el primer elemento de la lista que coincide con la condición, y regresa Nonesi ningún elemento coincide. Es mi forma de expresión única preferida.

Sin embargo,

for x in test_list:
    if x.value == value:
        print "i found it!"
        break

La ingenua versión de loop-break es perfectamente pitónica: es concisa, clara y eficiente. Para que coincida con el comportamiento del one-liner:

for x in test_list:
    if x.value == value:
        print "i found it!"
        break
else:
    x = None

Esto se asignará Nonea xsi no se breaksale del ciclo.

agf
fuente
72
+1 para el tranquilizador "La ingenua versión de loop-break, es perfectamente Pythonic".
LaundroMat
gran solución, pero ¿cómo modifico su línea para que pueda hacer que x.value realmente signifique x.fieldMemberName donde ese nombre se almacena en valor? field = "name" next ((x para x en test_list if x.field == value), None) para que en este caso, en realidad esté comprobando contra x.name, no x.field
Stewart Dale
3
@StewartDale No está totalmente claro lo que estás preguntando, pero creo que quieres decir ... if getattr(x, x.fieldMemberName) == value. Eso buscará el atributo xcon el nombre almacenado fieldMemberNamey lo comparará con value.
agf
1
@ThatTechGuy: la elsecláusula está destinada a estar en el forbucle, no a la if. (Edición rechazada).
agf
1
@agf Wow Literalmente no tenía idea de que existía ... book.pythontips.com/en/latest/for_-_else.html ¡genial!
ThatTechGuy
25

Dado que no se ha mencionado solo para su finalización. El buen filtro para filtrar los elementos que se filtrarán.

Programación funcional ftw.

####### Set Up #######
class X:

    def __init__(self, val):
        self.val = val

elem = 5

my_unfiltered_list = [X(1), X(2), X(3), X(4), X(5), X(5), X(6)]

####### Set Up #######

### Filter one liner ### filter(lambda x: condition(x), some_list)
my_filter_iter = filter(lambda x: x.val == elem, my_unfiltered_list)
### Returns a flippin' iterator at least in Python 3.5 and that's what I'm on

print(next(my_filter_iter).val)
print(next(my_filter_iter).val)
print(next(my_filter_iter).val)

### [1, 2, 3, 4, 5, 5, 6] Will Return: ###
# 5
# 5
# Traceback (most recent call last):
#   File "C:\Users\mousavin\workspace\Scripts\test.py", line 22, in <module>
#     print(next(my_filter_iter).value)
# StopIteration


# You can do that None stuff or whatever at this point, if you don't like exceptions.

Sé que, en general, en la lista de Python se prefieren las comprensiones o al menos eso es lo que leo, pero no veo el problema para ser sincero. Por supuesto, Python no es un lenguaje FP, pero Map / Reduce / Filter son perfectamente legibles y son los casos de uso más estándar en programación funcional.

Ahí vas. Conoce tu programación funcional.

lista de condiciones de filtro

No será más fácil que esto:

next(filter(lambda x: x.val == value,  my_unfiltered_list)) # Optionally: next(..., None) or some other default value to prevent Exceptions
Nima Mousavi
fuente
Me gusta mucho el estilo de esto, pero hay dos posibles problemas. 1 : Funciona solo en Python 3; en Python 2, filterdevuelve una lista que no es compatible con next. 2 : requiere que haya una coincidencia definitiva, de lo contrario obtendrá una StopIterationexcepción.
freethebees
1
1: No conozco Python 2. Cuando comencé a usar Python, Python 3 ya estaba disponible. Lamentablemente, no tengo ni idea de las especificaciones de Python 2. 2. @freethebees como lo señala agf. Puede usar el siguiente (..., Ninguno) o algún otro valor predeterminado, si no es fanático de las excepciones. También lo agregué como un comentario a mi código.
Nima Mousavi
@freethebees El punto 2 en realidad podría ser bueno. Cuando necesito cierto objeto en una lista, fallar rápido es algo bueno.
kap
7

Un ejemplo simple : tenemos la siguiente matriz

li = [{"id":1,"name":"ronaldo"},{"id":2,"name":"messi"}]

Ahora, queremos encontrar el objeto en la matriz que tiene una identificación igual a 1

  1. Usar método nextcon comprensión de lista
next(x for x in li if x["id"] == 1 )
  1. Usa la comprensión de la lista y devuelve el primer artículo
[x for x in li if x["id"] == 1 ][0]
  1. Función personalizada
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

La salida de todos los métodos anteriores es {'id': 1, 'name': 'ronaldo'}

Mohammad Nazari
fuente
1

Me encontré con un problema similar e ideé una pequeña optimización para el caso en el que ningún objeto en la lista cumple con el requisito (para mi caso de uso esto resultó en una mejora importante del rendimiento):

Junto con la lista test_list, mantengo un conjunto adicional test_value_set que consta de valores de la lista que necesito filtrar. Entonces, aquí la otra parte de la solución de agf se vuelve muy rápida.

usuario1578297
fuente
1

Podrías hacer algo como esto

dict = [{
   "id": 1,
   "name": "Doom Hammer"
 },
 {
    "id": 2,
    "name": "Rings ov Saturn"
 }
]

for x in dict:
  if x["id"] == 2:
    print(x["name"])

Eso es lo que uso para encontrar los objetos en una larga variedad de objetos.

Illud
fuente
¿Cómo es esto diferente entonces qué interrogador ya ha intentado?
Anum Sheraz
Quería mostrar cómo puede obtener el objeto y la matriz de objetos de la manera más simple.
Illud
0

También podría implementar una rica comparación a través del __eq__método para su Testclase y usar el inoperador. No estoy seguro de si esta es la mejor manera independiente, pero en caso de que necesite comparar Testinstancias basadas en valueotro lugar, esto podría ser útil.

class Test:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        """To implement 'in' operator"""
        # Comparing with int (assuming "value" is int)
        if isinstance(other, int):
            return self.value == other
        # Comparing with another Test object
        elif isinstance(other, Test):
            return self.value == other.value

import random

value = 5

test_list = [Test(random.randint(0,100)) for x in range(1000)]

if value in test_list:
    print "i found it"
tm-
fuente
0

Para el siguiente código, xGen es una expresión generadora anónima, yFilt es un objeto de filtro. Tenga en cuenta que para xGen se devuelve el parámetro None adicional en lugar de lanzar StopIteration cuando se agota la lista.

arr =((10,0), (11,1), (12,2), (13,2), (14,3))

value = 2
xGen = (x for x in arr if x[1] == value)
yFilt = filter(lambda x: x[1] == value, arr)
print(type(xGen))
print(type(yFilt))

for i in range(1,4):
    print('xGen: pass=',i,' result=',next(xGen,None))
    print('yFilt: pass=',i,' result=',next(yFilt))

Salida:

<class 'generator'>
<class 'filter'>
xGen: pass= 1  result= (12, 2)
yFilt: pass= 1  result= (12, 2)
xGen: pass= 2  result= (13, 2)
yFilt: pass= 2  result= (13, 2)
xGen: pass= 3  result= None
Traceback (most recent call last):
  File "test.py", line 12, in <module>
    print('yFilt: pass=',i,' result=',next(yFilt))
StopIteration
edW
fuente