Como usar filtro, mapear e reduzir no Python 3

321

filter, mapE reducefuncionar perfeitamente em Python 2. Aqui está um exemplo:

>>> def f(x):
        return x % 2 != 0 and x % 3 != 0
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

>>> def cube(x):
        return x*x*x
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

>>> def add(x,y):
        return x+y
>>> reduce(add, range(1, 11))
55

Mas no Python 3, recebo as seguintes saídas:

>>> filter(f, range(2, 25))
<filter object at 0x0000000002C14908>

>>> map(cube, range(1, 11))
<map object at 0x0000000002C82B70>

>>> reduce(add, range(1, 11))
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    reduce(add, range(1, 11))
NameError: name 'reduce' is not defined

Eu apreciaria se alguém pudesse me explicar por que isso acontece.

Captura de tela do código para maior clareza:

Sessões IDLE do Python 2 e 3 lado a lado

Dick Lucas
fonte
1
Em resumo, a lista não é o único tipo de dados. Se você deseja uma lista, diga que deseja uma lista. Mas na maioria dos casos, você quer outra coisa de qualquer maneira.
Veky

Respostas:

346

Você pode ler sobre as alterações em O que há de novo no Python 3.0 . Você deve lê-lo completamente quando passar de 2.x para 3.x, pois muitas alterações foram alteradas.

A resposta completa aqui são citações da documentação.

Exibições e iteradores em vez de listas

Algumas APIs conhecidas não retornam mais listas:

  • [...]
  • map()e filter()retorne iteradores. Se você realmente precisa de uma lista, uma correção rápida é list(map(...)), por exemplo , mas geralmente é melhor usar uma compreensão da lista (especialmente quando o código original usa lambda) ou reescrevê-lo para que não precise de uma lista. Particularmente complicado é map()invocado para os efeitos colaterais da função; a transformação correta é usar um forloop regular (já que criar uma lista seria apenas um desperdício).
  • [...]

Builtins

  • [...]
  • Removido reduce(). Use functools.reduce()se você realmente precisar; no entanto, 99% das vezes que um forloop explícito é mais legível.
  • [...]
nhahtdh
fonte
21
Adicionando em list(map(...) todos os lugares .. como no mundo isso ajuda a legibilidade .. pythonparece não conseguir lidar com a aplicação progressiva / de streaming de combinadores funcionais. Outros idiomas Eu posso encadear uma dúzia de operações contra uma coleção em uma linha e é legível. Aqui? o que você quer - uma dúzia de maneiras aninhadas in??
Javadba
11
Se você estiver trabalhando em um contexto imperativo, um loop for é provavelmente a opção mais legível. Mas há boas razões para preferir um contexto funcional - e romper com isso para voltar ao procedimento pode ser bastante feio.
MatrixManAtYrService
2
@javadba Você tem certeza de que em um "aplicativo de streaming" precisa adicionar a listchamada? Eu pensei que o significado de "streaming" é "nenhuma lista é criada; processe cada elemento da entrada completamente antes de passar para a próxima".
Noite imperecível
@MatrixManAtYrService Se você tem certeza de que o comportamento do python 2 é o que você precisa, sempre pode redefinir map.
Noite imperecível
6
Ainda não consigo entender como um argumento de legibilidade leva a essa mudança. Se fosse por motivos de desempenho que eu poderia entender ...
Minato
86

A funcionalidade de mape filterfoi intencionalmente alterada para retornar iteradores e a redução foi removida de ser incorporada e inserida functools.reduce.

Assim, para filtere map, você pode envolvê-los list()para ver os resultados como você fez antes.

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> list(filter(f, range(2, 25)))
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> list(map(cube, range(1, 11)))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> import functools
>>> def add(x,y): return x+y
...
>>> functools.reduce(add, range(1, 11))
55
>>>

A recomendação agora é substituir o uso de mapa e filtro por expressões de geradores ou compreensão de lista. Exemplo:

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> [i for i in range(2, 25) if f(i)]
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> [cube(i) for i in range(1, 11)]
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>>

Eles dizem que para loops são 99% das vezes mais fáceis de ler do que reduzir, mas eu apenas continuaria functools.reduce.

Edit : O número de 99% é extraído diretamente da página What's New In Python 3.0 , de Guido van Rossum.

Joshua D. Boyd
fonte
5
Você não precisa criar funções extras na compreensão da lista. Basta usar[i*i*i for i in range(1,11)]
Xiao
2
Você está absolutamente correto. Eu mantive a função nos exemplos de compreensão da lista para mantê-la parecida com os exemplos de filtro / mapa.
Joshua D. Boyd
5
i ** 3 também é equivalente a i * i * i
Breezer
5
O @Breezer realmente i**3ligará i.__pow__(3)e i*i*i i.__mul__(i).__mul__(i)(ou algo assim). Com ints, não importa, mas com números numpy / classes personalizadas, pode até produzir resultados diferentes.
Sintonimo 18/03/16
1
Percebi que sempre que ouvimos que "Guido tomou a decisão X", essa dor é um resultado provável. Este é um ótimo exemplo: list(list(list(.. )))fazer o que estava detalhado em python.
Javadba 5/10
12

Como um adendo para as outras respostas, isso parece um bom caso de uso para um gerenciador de contexto que irá mapear novamente os nomes dessas funções para aquelas que retornam uma lista e são introduzidas reduceno espaço para nome global.

Uma implementação rápida pode ser assim:

from contextlib import contextmanager    

@contextmanager
def noiters(*funcs):
    if not funcs: 
        funcs = [map, filter, zip] # etc
    from functools import reduce
    globals()[reduce.__name__] = reduce
    for func in funcs:
        globals()[func.__name__] = lambda *ar, func = func, **kwar: list(func(*ar, **kwar))
    try:
        yield
    finally:
        del globals()[reduce.__name__]
        for func in funcs: globals()[func.__name__] = func

Com um uso assim:

with noiters(map):
    from operator import add
    print(reduce(add, range(1, 20)))
    print(map(int, ['1', '2']))

Que imprime:

190
[1, 2]

Apenas meus 2 centavos :-)

Dimitris Fasarakis Hilliard
fonte
1
pythoncomo língua é uma bagunça - mas tem v bons a excelentes bibliotecas: numpy, pandas, statsmodelse amigos .. eu tinha sido buliding bibliotecas de conveniência como você mostrar aqui para reduzir a dor da língua nativa -, mas perderam a energia e não tentar afastar-se de um data.frame/ datatable, ou xarray. Mas parabéns por tentar ..
javadba 05/10
7

Como o reducemétodo foi removido da função interna do Python3, não se esqueça de importá-lo functoolsno seu código. Veja o snippet de código abaixo.

import functools
my_list = [10,15,20,25,35]
sum_numbers = functools.reduce(lambda x ,y : x+y , my_list)
print(sum_numbers)
Bikash Singh
fonte
2

Aqui estão os exemplos de filtrar, mapear e reduzir funções.

number = [10,11,12,22,34,43,54,34,67,87,88,98,99,87,44,66]

//Filtro

oddNumbers = list (filtro (lambda x: x% 2! = 0, números))

print (ímpares)

//Mapa

multiplyOf2 = list (mapa (lambda x: x * 2, números))

print (multiplyOf2)

//Reduzir

A função de redução, como não é comumente usada, foi removida das funções internas do Python 3. Ela ainda está disponível no módulo functools, para que você possa:

da importação de funções para reduzir

sumOfNumbers = reduzir (lambda x, y: x + y, números)

print (sumOfNumbers)

Yogendra Singh
fonte
0

Uma das vantagens de mapear, filtrar e reduzir é o quão legíveis elas se tornam quando você as "liga" para fazer algo complexo. No entanto, a sintaxe interna não é legível e é "invertida". Então, sugiro usar o PyFunctionalpacote ( https://pypi.org/project/PyFunctional/ ). Aqui está uma comparação dos dois:

flight_destinations_dict = {'NY': {'London', 'Rome'}, 'Berlin': {'NY'}}

Versão PyFunctional

Sintaxe muito legível. Você pode dizer:

"Tenho uma sequência de destinos de voo. Das quais quero obter a chave do ditado se a cidade estiver nos valores do ditado. Finalmente, filtre as listas vazias que criei no processo."

from functional import seq  # PyFunctional package to allow easier syntax

def find_return_flights_PYFUNCTIONAL_SYNTAX(city, flight_destinations_dict):
    return seq(flight_destinations_dict.items()) \
        .map(lambda x: x[0] if city in x[1] else []) \
        .filter(lambda x: x != []) \

Versão padrão do Python

Está tudo ao contrário. Você precisa dizer:

"OK, então há uma lista. Quero filtrar listas vazias. Por quê? Porque eu obtive a chave do dict se a cidade estava nos valores dict. Ah, a lista para a qual estou fazendo isso é flight_destinations_dict. "

def find_return_flights_DEFAULT_SYNTAX(city, flight_destinations_dict):
    return list(
        filter(lambda x: x != [],
               map(lambda x: x[0] if city in x[1] else [], flight_destinations_dict.items())
               )
    )
Daniel
fonte