Como posso eficientemente filtrar valores computados dentro de uma compreensão de lista Python?

StackOverflow https://stackoverflow.com/questions/130262

  •  02-07-2019
  •  | 
  •  

Pergunta

A sintaxe compreensão de lista Python facilita a valores de filtro dentro de uma compreensão. Por exemplo:

result = [x**2 for x in mylist if type(x) is int]

retornará uma lista dos quadrados dos inteiros em mylist. No entanto, se o teste envolve alguma computação (caro) e você deseja filtrar o resultado? Uma opção é:

result = [expensive(x) for x in mylist if expensive(x)]

Isto irá resultar em uma lista de caras valores não "falsos" (x), no entanto caro () é chamado duas vezes para cada x. Existe uma sintaxe compreensão que lhe permite fazer este teste enquanto apenas chamando caro uma vez por x?

Foi útil?

Solução

Se os cálculos já estão bem agrupados em funções, como sobre o uso filter e map?

result = filter (None, map (expensive, mylist))

Você pode usar itertools.imap se a lista é muito grande.

Outras dicas

vim com a minha própria resposta depois de um minuto de pensamento. Isso pode ser feito com compreensões aninhados:

result = [y for y in (expensive(x) for x in mylist) if y]

Eu acho que funciona, apesar de eu achar compreensões aninhados são apenas marginalmente readable

A mais óbvia (e eu diria mais legível) resposta é não usar uma compreensão lista ou gerador de expressão, mas sim um gerador real:

def gen_expensive(mylist):
    for item in mylist:
        result = expensive(item)
        if result:
            yield result

É preciso mais espaço horizontal, mas é muito mais fácil ver o que ele faz de relance, e que acabam por não repetir-se.

result = [x for x in map(expensive,mylist) if x]

map () irá retornar uma lista dos valores de cada objeto em mylist passado para caro (). Em seguida, você pode listar-compreender que, e os valores desnecessários descarte.

Este é um pouco como uma compreensão aninhada, mas deve ser mais rápido (uma vez que o interpretador Python pode otimizá-lo com bastante facilidade).

Este é exatamente o que os geradores são adequados para identificador:

result = (expensive(x) for x in mylist)
result = (do_something(x) for x in result if some_condition(x))
...
result = [x for x in result if x]  # finally, a list
  1. Isto torna totalmente claro o que está acontecendo durante cada estágio do pipeline.
  2. explícito sobre implícita
  3. Usa geradores em todos os lugares, até o passo final, assim não há grandes listas intermediários

cf: 'Gerador Truques para Sistema Programmers' por David Beazley

Você pode sempre memoize a função expensive() para que chamá-lo pela segunda vez é meramente uma pesquisa para o valor calculado de x.

Aqui é apenas uma das muitas implementações de memoize como um decorador .

Você poderia memoize caro (x) (e se você está chamando caro (x) com freqüência, você provavelmente deve memoize lo de qualquer maneira Esta página dá uma implementação de memoize para python:.

http://code.activestate.com/recipes/52201/

Isto tem a vantagem adicional de que caro (x) pode ser executado menos de N vezes, uma vez que quaisquer entradas duplicadas fará uso do memorando da execução anterior.

Note que este assume caro (x) é uma verdadeira função, e não depende de estado externo que pode mudar. Se caro (x) não depende de estado externo, e você pode detectar quando que as mudanças de estado, ou você sabe que não vai mudança durante lista de sua compreensão, então você pode redefinir os memorandos antes da compreensão.

Vou ter uma preferência para:

itertools.ifilter(bool, (expensive(x) for x in mylist))

Isto tem a vantagem de:

Há o uso velho liso de um loop for para acrescentar a uma lista, também:

result = []
for x in mylist:
    expense = expensive(x)
    if expense:
        result.append(expense)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top