Por acaso, encontrei uma necessidade básica de filtragem: tenho uma lista e tenho que filtrá-la por um atributo dos itens.
Meu código ficou assim:
my_list = [x for x in my_list if x.attribute == value]
Mas então pensei: não seria melhor escrever assim?
my_list = filter(lambda x: x.attribute == value, my_list)
É mais legível e, se necessário para o desempenho, o lambda pode ser retirado para obter algo.
A pergunta é: existem algumas ressalvas no uso da segunda maneira? Alguma diferença de desempenho? Estou sentindo falta do Pythonic Way ™ inteiramente e devo fazê-lo de outra maneira (como usar o itemgetter em vez do lambda)?
filter
era mais legível. Quando você tem uma expressão simples que pode ser usada como está em um listcomp, mas precisa ser empacotada em um lambda (ou similarmente construído compartial
ouoperator
funções, etc.) para passarfilter
, é quando os listcomps vencem.filter
é um objeto gerador de filtro e não uma lista.Respostas:
É estranho o quanto a beleza varia para pessoas diferentes. Acho a compreensão da lista muito mais clara que
filter
+lambda
, mas use o que achar mais fácil.Há duas coisas que podem retardar o seu uso
filter
.A primeira é a sobrecarga da chamada de função: assim que você usar uma função Python (seja criada por
def
oulambda
), é provável que o filtro seja mais lento que a compreensão da lista. Quase certamente não é o suficiente para importar, e você não deve pensar muito em desempenho até ter cronometrado seu código e considerado um gargalo, mas a diferença estará lá.A outra sobrecarga que pode ser aplicada é que o lambda está sendo forçado a acessar uma variável com escopo definido (
value
). Isso é mais lento que acessar uma variável local e no Python 2.x a compreensão da lista acessa apenas variáveis locais. Se você estiver usando o Python 3.x, a compreensão da lista será executada em uma função separada, portanto, ele também acessarávalue
através de um fechamento e essa diferença não se aplicará.A outra opção a considerar é usar um gerador em vez de uma compreensão da lista:
Então, no seu código principal (que é onde a legibilidade realmente importa), você substituiu a compreensão da lista e o filtro por um nome de função que se espera ser significativo.
fonte
[]
para()
. Além disso, concordo que a lista comp é mais bonita.filter
mais rápido o uso de uma função de retorno de chamada Python.Esta é uma questão um pouco religiosa em Python. Embora Guido
map
filter
reduce
tenha considerado a remoção , e do Python 3 , houve uma reação suficiente que, no final,reduce
foi movida apenas de built-ins para functools.reduce .Pessoalmente, acho a compreensão da lista mais fácil de ler. É mais explícito o que está acontecendo com a expressão,
[i for i in list if i.attribute == value]
pois todo o comportamento está na superfície e não dentro da função de filtro.Eu não me preocuparia muito com a diferença de desempenho entre as duas abordagens, pois é marginal. Eu realmente otimizaria isso apenas se isso provasse ser o gargalo no seu aplicativo, o que é improvável.
Também desde que o BDFL queria
filter
sair da linguagem, certamente isso automaticamente torna a compreensão da lista mais Pythonic ;-)fonte
Como qualquer diferença de velocidade deve ser minúscula, o uso de filtros ou a compreensão de listas se resume a uma questão de gosto. Em geral, estou inclinado a usar compreensões (o que parece concordar com a maioria das outras respostas aqui), mas há um caso em que prefiro
filter
.Um caso de uso muito frequente é extrair os valores de algum X iterável sujeito a um predicado P (x):
mas às vezes você deseja aplicar alguma função aos valores primeiro:
Como exemplo específico, considere
Eu acho que isso parece um pouco melhor do que usar
filter
. Mas agora considereNesse caso, queremos
filter
contra o valor pós-calculado. Além da questão de calcular o cubo duas vezes (imagine um cálculo mais caro), há a questão de escrever a expressão duas vezes, violando a estética DRY . Nesse caso, eu estaria apto a usarfonte
[prime(i) for i in [x**3 for x in range(1000)]]
x*x*x
não pode ser um número primo, como temx^2
ex
como fator, o exemplo realmente não faz sentido de maneira matemática, mas talvez ainda seja útil. (Talvez pudéssemos encontrar algo melhor ainda?)prime_cubes = filter(prime, (x*x*x for x in range(1000)))
prime_cubes = [1]
salvar ambos os ciclos de memória e CPU ;-)[]
Embora
filter
possa ser o "caminho mais rápido", o "caminho Pythonic" seria não se importar com essas coisas, a menos que o desempenho seja absolutamente crítico (nesse caso, você não usaria o Python!).fonte
Eu pensei em acrescentar que, no python 3, filter () é realmente um objeto iterador, então você teria que passar sua chamada de método de filtro para list () para criar a lista filtrada. Então, no python 2:
as listas bec têm os mesmos valores e foram concluídas quase na mesma época em que filter () era equivalente [x para x em y se z]. No entanto, em 3, esse mesmo código deixaria a lista c contendo um objeto de filtro, não uma lista filtrada. Para produzir os mesmos valores em 3:
O problema é que list () pega uma iterável como argumento e cria uma nova lista a partir desse argumento. O resultado é que o uso desse filtro no python 3 leva até duas vezes mais que o método [x for x in y if z] porque você precisa iterar sobre a saída de filter () e a lista original.
fonte
Uma diferença importante é que a compreensão da lista retornará um
list
tempo enquanto o filtro retornar afilter
, que você não pode manipular como alist
(por exemplo: invocálen
-lo, que não funciona com o retorno defilter
).Minha própria auto-aprendizagem me levou a uma questão semelhante.
Dito isto, se houver uma maneira de obter o resultado
list
de umfilter
, como você faria no .NET quando tiverlst.Where(i => i.something()).ToList()
, estou curioso para saber.EDIT: Este é o caso do Python 3, não 2 (veja a discussão nos comentários).
fonte
a = [1, 2, 3, 4, 5, 6, 7, 8]
f = filter(lambda x: x % 2 == 0, a)
lc = [i for i in a if i % 2 == 0]
>>> type(f)
<class 'filter'>
>>> type(lc)
<class 'list'>
list()
no resultado:list(filter(my_func, my_iterable))
. E, claro, você pode substituirlist
porset
, outuple
, ou qualquer outra coisa que exija uma iteração. Mas para qualquer pessoa que não seja programador funcional, o caso é ainda mais forte para usar uma compreensão de lista em vez de fazerfilter
uma conversão explícita paralist
.Acho o segundo caminho mais legível. Ele diz exatamente qual é a intenção: filtrar a lista.
PS: não use 'list' como um nome de variável
fonte
geralmente
filter
é um pouco mais rápido se estiver usando uma função embutida.Eu esperaria que a compreensão da lista fosse um pouco mais rápida no seu caso
fonte
Filtro é exatamente isso. Ele filtra os elementos de uma lista. Você pode ver que a definição menciona o mesmo (no link oficial dos documentos que mencionei antes). Visto que a compreensão da lista é algo que produz uma nova lista depois de agir sobre algo da lista anterior (a compreensão do filtro e da lista cria uma nova lista e não executa operações no lugar da lista antiga. Uma nova lista aqui é algo como uma lista com digamos, um tipo de dados totalmente novo. Como converter números inteiros em string etc.)
No seu exemplo, é melhor usar o filtro do que a compreensão da lista, conforme a definição. No entanto, se desejar, diga other_attribute a partir dos elementos da lista; no seu exemplo deve ser recuperado como uma nova lista, você poderá usar a compreensão da lista.
É assim que me lembro sobre a compreensão de filtros e listas. Remova algumas coisas de uma lista e mantenha os outros elementos intactos, use o filtro. Use alguma lógica por conta própria nos elementos e crie uma lista diluída adequada para algum propósito, use a compreensão da lista.
fonte
Aqui está um pequeno pedaço que eu uso quando preciso filtrar algo depois a compreensão da lista. Apenas uma combinação de filtro, lambda e listas (também conhecidas como a lealdade de um gato e a limpeza de um cachorro).
Nesse caso, estou lendo um arquivo, removendo linhas em branco, comentando linhas e qualquer coisa após um comentário em uma linha:
fonte
file_contents = list(filter(None, (s.partition('#')[0].strip() for s in lines)))
Além da resposta aceita, há uma caixa de canto quando você deve usar filtro em vez de uma compreensão de lista. Se a lista for removível, você não poderá processá-la diretamente com uma compreensão da lista. Um exemplo do mundo real é se você usa
pyodbc
para ler resultados de um banco de dados. OsfetchAll()
resultadoscursor
são de uma lista unhashable. Nessa situação, para manipular diretamente os resultados retornados, o filtro deve ser usado:Se você usar a compreensão da lista aqui, receberá o erro:
fonte
>>> hash(list()) # TypeError: unhashable type: 'list'
segundo lugar isso funciona bem:processed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime >= start_date_time]
Levei algum tempo para me familiarizar com o
higher order functions
filter
emap
. Então, eu me acostumei com eles e gosteifilter
, pois era explícito que ele filtra mantendo o que é verdadeiro e me senti bem por conhecer algunsfunctional programming
termos.Então eu li esta passagem (Fluent Python Book):
E agora penso: por que se preocupar com o conceito de
filter
/map
se você pode alcançá-lo com idiomas já amplamente difundidos, como compreensão de lista? Além dissomaps
efilters
são tipos de funções. Nesse caso, prefiro usarAnonymous functions
lambdas.Finalmente, apenas para testá-lo, cronometrei os dois métodos (
map
elistComp
) e não vi nenhuma diferença de velocidade relevante que justificasse argumentar sobre isso.fonte
Curiosamente, no Python 3, vejo o desempenho do filtro mais rápido que as compreensões de lista.
Eu sempre pensei que a compreensão da lista seria mais eficiente. Algo como: [nome para o nome em brand_names_db se o nome não for None] O bytecode gerado é um pouco melhor.
Mas eles são realmente mais lentos:
fonte
if not None
na compreensão da lista, você está definindo uma função lambda (observe aMAKE_FUNCTION
instrução). Segundo, os resultados são diferentes, pois a versão de compreensão da lista removerá apenas oNone
valor, enquanto a versão do filtro removerá todos os valores "falsos". Dito isto, todo o objetivo do microbenchmarking é inútil. São um milhão de iterações, vezes 1k itens! A diferença é insignificante .Minha vez
fonte
i
nunca foi dito ser umdict
, e não há necessidadelimit
. Fora isso, como isso é diferente do que o OP sugeria e como ele responde à pergunta?