Encontre um objeto na lista que tenha atributo igual a algum valor (que atenda a qualquer condição)

221

Eu tenho uma lista de objetos. Eu quero encontrar um (primeiro ou qualquer outro objeto) nesta lista que tenha atributo (ou resultado do método - qualquer que seja) igual a value.

Qual é a melhor maneira de encontrá-lo?

Aqui está o caso de teste:

  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

Eu acho que usar geradores e reduce()não fará nenhuma diferença, porque ainda estaria percorrendo a lista.

ps .: A equação para valueé apenas um exemplo. Claro que queremos obter um elemento que atenda a qualquer condição.

seler
fonte
2
Aqui está uma boa discussão sobre esta questão: tomayko.com/writings/cleanest-python-find-in-list-function
Andrew Hare
A postagem original está ridiculamente desatualizada, mas a segunda resposta corresponde exatamente à minha versão de uma linha. Não estou convencido de que seja melhor do que a versão básica do loop.
agf

Respostas:

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

Isso obtém o primeiro item da lista que corresponde à condição e retorna Nonese nenhum item corresponder. É o meu formulário de expressão única preferido.

Contudo,

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

A versão ingênua de quebra de loop é perfeitamente Pythonic - é concisa, clara e eficiente. Para que ele corresponda ao comportamento de uma linha:

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

Isto irá atribuir Nonepara xse você não faz breakfora do circuito.

agf
fonte
72
+1 para a tranquilizadora "A versão ingênua de quebra de loop é perfeitamente pitônica".
precisa saber é o seguinte
ótima solução, mas como modifico sua linha para que eu possa fazer com que x.value realmente signifique x.fieldMemberName onde esse nome está armazenado em valor? field = "nome" próximos ((x para x em test_list se x.field valor ==), None), de modo que, neste caso, eu estou realmente verificar contra x.name, não x.field
Stewart Dale
3
@StewartDale Não está totalmente claro o que você está perguntando, mas acho que você quis dizer ... if getattr(x, x.fieldMemberName) == value. Isso buscará o atributo xcom o nome armazenado fieldMemberNamee o comparará com value.
agf
1
@ThatTechGuy - A elsecláusula deve estar no forloop, não no if. (Edição rejeitada).
agf 02/02
1
@agf Uau, eu literalmente não tinha idéia de que existia .. book.pythontips.com/en/latest/for_-_else.html legal!
precisa saber é o seguinte
25

Uma vez que não foi mencionado apenas para conclusão. O bom e velho filtro para filtrar seus elementos a serem filtrados.

Ftw de programação funcional.

####### 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.

Eu sei que geralmente na lista python é preferível a compreensão ou pelo menos é o que eu leio, mas não vejo a questão como honesta. É claro que o Python não é uma linguagem FP, mas Map / Reduce / Filter são perfeitamente legíveis e são os casos de uso mais padrão da programação funcional.

Então lá vai você. Conheça tua programação funcional.

lista de condições do filtro

Não será mais fácil do que isso:

next(filter(lambda x: x.val == value,  my_unfiltered_list)) # Optionally: next(..., None) or some other default value to prevent Exceptions
Nima Mousavi
fonte
Eu gosto bastante do estilo disso, mas há dois problemas em potencial. 1 : Funciona apenas em Python 3; no Python 2, filterretorna uma lista que não é compatível next. 2 : requer que haja uma correspondência definida; caso contrário, você receberá uma StopIterationexceção.
freethebees
1
1: Não conheço o Python 2. Quando comecei a usar o Python, o Python 3 já estava disponível. Infelizmente, eu não tenho noção das especificações do Python 2. 2. @freethebees, como apontado pela agf. Você pode usar o próximo (..., Nenhum) ou algum outro valor padrão, se você não é fã de exceções. Também o adicionei como um comentário ao meu código.
Nima Mousavi
@freethebees O ponto 2 pode realmente ser bom. Quando eu exijo um determinado objeto em uma lista, falhar rapidamente é uma coisa boa.
kap
7

Um exemplo simples : temos a seguinte matriz

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

Agora, queremos encontrar o objeto na matriz que tenha um ID igual a 1

  1. Usar método nextcom compreensão de lista
next(x for x in li if x["id"] == 1 )
  1. Use a compreensão da lista e retorne o primeiro item
[x for x in li if x["id"] == 1 ][0]
  1. Função Personalizada
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

A saída de todos os métodos acima é {'id': 1, 'name': 'ronaldo'}

Mohammad Nazari
fonte
1

Acabei de encontrar um problema semelhante e desenvolvi uma pequena otimização para o caso em que nenhum objeto da lista atende aos requisitos (para o meu caso de uso, isso resultou em grande melhoria de desempenho):

Junto com a lista test_list, mantenho um conjunto adicional test_value_set que consiste em valores da lista que eu preciso filtrar. Portanto, aqui a outra parte da solução da agf se torna muito rápida.

user1578297
fonte
1

Você poderia fazer algo assim

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

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

Isso é o que eu uso para encontrar os objetos em uma longa variedade de objetos.

Illud
fonte
Como isso é diferente do que o questionador já tentou?
Anum Sheraz
Eu queria mostrar como ele pode obter o objeto e a matriz de objetos da maneira mais simples.
Illud
0

Você também pode implementar uma comparação rica via __eq__método para sua Testclasse e usar o inoperador. Não tenho certeza se essa é a melhor maneira independente, mas, se você precisar comparar Testinstâncias com base em valueoutro lugar, isso pode 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-
fonte
0

Para o código abaixo, xGen é uma expressão geradora anônima, yFilt é um objeto de filtro. Observe que, para xGen, o parâmetro None adicional é retornado em vez de gerar StopIteration quando a lista estiver esgotada.

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))

Resultado:

<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
fonte