Cómo usar filtro, mapear y reducir en Python 3

321

filter, mapy reducefuncionan perfectamente en Python 2. Aquí hay un ejemplo:

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

Pero en Python 3, recibo los siguientes resultados:

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

Agradecería que alguien me explicara por qué es así.

Captura de pantalla del código para mayor claridad:

Sesiones IDLE de Python 2 y 3 lado a lado

Dick Lucas
fuente
1
En resumen, la lista no es el único tipo de datos. Si quieres una lista, di que quieres una lista. Pero en la mayoría de los casos, quieres algo más de todos modos.
Veky

Respuestas:

346

Puede leer sobre los cambios en What's New In Python 3.0 . Debe leerlo detenidamente cuando pase de 2.xa 3.x ya que se ha cambiado mucho.

La respuesta completa aquí son citas de la documentación.

Vistas e iteradores en lugar de listas

Algunas API conocidas ya no devuelven listas:

  • [...]
  • map()y filter()devolver iteradores. Si realmente necesita una lista, una solución rápida es list(map(...)), por ejemplo , pero una solución mejor es usar una comprensión de la lista (especialmente cuando el código original usa lambda), o reescribir el código para que no necesite una lista. Se map()invoca particularmente complicado por los efectos secundarios de la función; la transformación correcta es usar un regularfor ciclo (ya que crear una lista sería un desperdicio).
  • [...]

Builtins

  • [...]
  • Eliminado reduce(). Úselo functools.reduce()si realmente lo necesita; sin embargo, el 99 por ciento de las veces un forciclo explícito es más legible.
  • [...]
nhahtdh
fuente
21
Agregando en list(map(...) todas partes ... cómo en el mundo es que la legibilidad ayuda ... pythonparece que no puede manejar la aplicación progresiva / de transmisión de combinadores funcionales. En otros idiomas, puedo encadenar una docena de operaciones contra una colección en una fila y es legible. ¿Aquí? ¿Qué quieres, una docena de formas anidadas in?
javadba
11
Si está trabajando en un contexto imperativo, entonces un bucle for es probablemente la opción más legible. Pero hay buenas razones para preferir un contexto funcional, y romper con eso para volver al procedimiento puede ser bastante feo.
MatrixManAtYrService
2
@javadba ¿Está seguro de que en una "aplicación de transmisión" necesita agregar la listllamada? Pensé que el significado de "transmisión" es "no se crea ninguna lista; procese cada elemento de la entrada completamente antes de pasar al siguiente".
Noche imperecedera
@MatrixManAtYrService Si está seguro de que el comportamiento de Python 2 es lo que necesita, siempre puede redefinirlo map.
Noche imperecedera
66
Todavía no puedo entender cómo un argumento de legibilidad conduce a tal cambio. Si fuera por razones de rendimiento, podría entender ...
Minato
86

La funcionalidad de mapy filterse cambió intencionalmente para devolver iteradores, y reducir se eliminó de ser incorporado y se colocó en functools.reduce.

Entonces, para filtery map, puede envolverlos list()para ver los resultados como lo hizo 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
>>>

La recomendación ahora es que reemplace su uso de mapas y filtros con expresiones de generadores o comprensiones de listas. Ejemplo:

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

Dicen que los bucles for son un 99 por ciento de las veces más fáciles de leer que reducir, pero simplemente me quedaría con ellos functools.reduce.

Editar : La cifra del 99 por ciento se extrae directamente de la página Novedades en Python 3.0 creada por Guido van Rossum.

Joshua D. Boyd
fuente
55
No necesita crear funciones adicionales en las comprensiones de listas. Solo use[i*i*i for i in range(1,11)]
Xiao
2
Tienes toda la razón. Mantuve la función en la lista de ejemplos de comprensión para que se vea similar a los ejemplos de filtro / mapa.
Joshua D. Boyd
55
i ** 3 también es equivalente a i * i * i
Breezer
55
@Breezer realmente i**3llamará i.__pow__(3)y i*i*i i.__mul__(i).__mul__(i)(o algo así). Con ints no importa, pero con números numpy / clases personalizadas incluso podría producir resultados diferentes.
sinónimo
1
He notado que cada vez que escuchamos que "Guido tomó la decisión X", ese dolor es un resultado probable. Este es un gran ejemplo: list(list(list(.. )))hacer lo que ya era detallado en python.
javadba
12

Como una adición a las otras respuestas, esto suena como un buen caso de uso para un administrador de contexto que reasignará los nombres de estas funciones a las que devuelven una lista y la introducen reduceen el espacio de nombres global.

Una implementación rápida podría verse así:

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

Con un uso que se ve así:

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

Que imprime:

190
[1, 2]

Solo mis 2 centavos :-)

Dimitris Fasarakis Hilliard
fuente
1
pythoncomo lengua es un desastre - pero tiene v buenos a excelentes bibliotecas: numpy, pandas, statsmodelsy amigos .. Me habían BULIDING bibliotecas de conveniencia como usted muestra aquí para reducir el dolor de la lengua nativa - pero han perdido la energía y no tratar de alejarse de a data.frame/ datatable, o xarray. Pero felicitaciones por intentar ..
javadba
7

Dado que el reducemétodo se ha eliminado de la función integrada de Python3, no olvide importarlo functoolsen su código. Mire el fragmento de código a continuación.

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
fuente
2

Estos son los ejemplos de filtro, mapa y funciones de reducción.

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

//Filtrar

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

print (números impares)

//Mapa

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

print (multiplyOf2)

//Reducir

La función de reducción, ya que no se usa comúnmente, se eliminó de las funciones integradas en Python 3. Todavía está disponible en el módulo functools, por lo que puede hacer:

de importación de functools reducir

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

print (sumOfNumbers)

Yogendra Singh
fuente
0

Una de las ventajas de mapear, filtrar y reducir es cuán legibles se vuelven cuando los "encadena" para hacer algo complejo. Sin embargo, la sintaxis incorporada no es legible y está "al revés". Por lo tanto, sugiero usar el PyFunctionalpaquete ( https://pypi.org/project/PyFunctional/ ). Aquí hay una comparación de los dos:

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

Versión PyFunctional

Sintaxis muy legible. Puedes decir:

"Tengo una secuencia de destinos de vuelo. De los cuales quiero obtener la clave dict si city está en los valores dict. Finalmente, filtre las listas vacías que creé en el proceso".

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 != []) \

Versión predeterminada de Python

Todo está al revés. Necesitas decir:

"Bien, entonces, hay una lista. Quiero filtrar las listas vacías. ¿Por qué? Porque primero obtuve la clave dict si la ciudad estaba en los valores dict. Oh, la lista a la que hago esto es 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
fuente