Pesquisando uma lista de objetos em Python

90

Vamos supor que estou criando uma classe simples para funcionar de maneira semelhante a uma estrutura no estilo C, para conter apenas elementos de dados. Estou tentando descobrir como pesquisar uma lista de objetos para objetos com um atributo igual a um determinado valor. Abaixo está um exemplo trivial para ilustrar o que estou tentando fazer.

Por exemplo:

class Data:
    pass

myList = []

for i in range(20):
    data = Data()
    data.n = i
    data.n_squared = i * i
    myList.append(data)

Como eu pesquisaria a lista myList para determinar se ela contém um elemento com n == 5?

Tenho pesquisado no Google e pesquisado os documentos do Python e acho que posso fazer isso com uma compreensão de lista, mas não tenho certeza. Devo acrescentar que estou precisando usar o Python 2.4.3, a propósito, portanto, nenhum novo recurso gee-whiz 2.6 ou 3.x está disponível para mim.

m0j0
fonte
Talvez uma peculiaridade não intencional do seu exemplo: myList = [Data (). N == 0, Data (). N = 1, ...] onde data.n seria atribuído por range () e data.n seria o indexar em myList. Portanto, permitindo que você extraia qualquer instância de Data () apenas referenciando myList por um valor de índice. Claro que você pode modificar posteriormente myList [0] .n = 5.2 ou algo assim. E o exemplo talvez tenha sido simplificado demais.
DevPlayer de

Respostas:

130

Você pode obter uma lista de todos os elementos correspondentes com uma compreensão de lista:

[x for x in myList if x.n == 30]  # list of all elements with .n==30

Se você simplesmente deseja determinar se a lista contém algum elemento que corresponda e fazê-lo (relativamente) de forma eficiente, você pode fazer

def contains(list, filter):
    for x in list:
        if filter(x):
            return True
    return False

if contains(myList, lambda x: x.n == 3)  # True if any element has .n==3
    # do stuff
Adam Rosenfield
fonte
25
ou qualquer (custom_filter (x) para x em myList if xn == 30) que é apenas a sua função "contém" como embutida.
nosklo de
Erro de sintaxe em nosklo - precisa de um conjunto extra de () em torno do gerador.
gahooa
Não tão. Experimente e veja.
Robert Rossney
1
seria bom mesclar essa resposta com a de gahooa ( stackoverflow.com/a/598602/2349267 ).
Roman Hwang
76

Simples, elegante e poderoso:

Uma expressão geradora em conjunto com um embutido… (python 2.5+)

any(x for x in mylist if x.n == 10)

Usa o Python any()integrado, definido da seguinte maneira:

any (iterável) -> Retorna True se qualquer elemento do iterável for verdadeiro. Equivalente a:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False
gahooa
fonte
Agradável. Para sua informação, você pode fazer qualquer (x para x em minha lista se xn == 10) para salvar alguns parênteses (também == não =).
Jacob Gabrielson
Eu prefiro usar, any(x for x in mylist if x['n'] == 10)mas é uma boa ideia
Alex Montoya
46

Apenas para completar, não vamos esquecer a coisa mais simples que poderia funcionar:

for i in list:
  if i.n == 5:
     # do something with it
     print "YAY! Found one!"
Charlie Martin
fonte
38
[x for x in myList if x.n == 30]               # list of all matches
[x.n_squared for x in myList if x.n == 30]     # property of matches
any(x.n == 30 for x in myList)                 # if there is any matches
[i for i,x in enumerate(myList) if x.n == 30]  # indices of all matches

def first(iterable, default=None):
  for item in iterable:
    return item
  return default

first(x for x in myList if x.n == 30)          # the first match, if any
Markus Jarderot
fonte
1
Essa é uma boa resposta por causa do método "primeiro", que provavelmente é o caso de uso mais comum.
galarant
ótimo obrigado! os índices de correspondência eram o que eu procurava. Existe um atalho para usar isso para indexar diretamente a lista para acessar outro campo? Agora eu obtenho uma lista de entradas da lista (há apenas uma entrada, então é uma lista com um item). Para obter o índice, preciso executar o resultado [0] antes de usá-lo para indexar a lista. A partir do exemplo da pergunta, desejo acessar n_squared de um determinado n: myList [index of myList.n == 5] .n_squared
Frieke
31
filter(lambda x: x.n == 5, myList)
Vartec
fonte
25
para quem quer aprender Python, entender lambda é básico.
vartec
2
Bem, sim e não - com compreensões de lista e fabricantes de funções-chave de classificação, como operator.attrgetter, quase nunca uso lambdas.
Ben Hoyt
9

Você pode usar inpara procurar um item em uma coleção e uma compreensão de lista para extrair o campo no qual está interessado. Isso (funciona para listas, conjuntos, tuplas e qualquer coisa que defina __contains__ou __getitem__).

if 5 in [data.n for data in myList]:
    print "Found it"

Veja também:

Tom Dunham
fonte
4

Você deve adicionar um __eq__e um __hash__método à sua Dataclasse, ele pode verificar se os __dict__atributos são iguais (mesmas propriedades) e, em seguida, se seus valores são iguais também.

Se você fez isso, você pode usar

test = Data()
test.n = 5

found = test in myList

A inpalavra-chave verifica se testestá em myList.

Se você deseja apenas uma npropriedade, Datapode usar:

class Data(object):
    __slots__ = ['n']
    def __init__(self, n):
        self.n = n
    def __eq__(self, other):
        if not isinstance(other, Data):
            return False
        if self.n != other.n:
            return False
        return True
    def __hash__(self):
        return self.n

    myList = [ Data(1), Data(2), Data(3) ]
    Data(2) in myList  #==> True
    Data(5) in myList  #==> False
Johannes Weiss
fonte
3

Considere usar um dicionário:

myDict = {}

for i in range(20):
    myDict[i] = i * i

print(5 in myDict)
dan-gph
fonte
Ou: d = dict ((i, i * i) para i no intervalo (20))
hughdbrown
Resolve o problema trivial que usei para ilustrar minha pergunta, mas não resolveu realmente minha pergunta raiz. A resposta que eu estava procurando (5+ anos atrás) era a compreensão da lista. :)
m0j0
1

Outra maneira de fazer isso é usando a função next ().

matched_obj = next(x for x in list if x.n == 10)
Oliver Breeden
fonte