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

Resuelto Dick Lucas asked hace 12 años • 7 respuestas

Así es como estoy acostumbrado a filtertrabajar mapen reducePython 2:

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

Sin embargo, todo esto parece fallar en Python 3:

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

¿Por qué los resultados son diferentes? ¿Cómo puedo hacer que el código Python 3 funcione como lo hizo el código Python 2?


Ver también: ¿ Cuál es el problema con reducir()? para obtener una motivación específica para que el cambio se coloque reduceen un módulo de biblioteca estándar en lugar de dejarlo como incorporado.

Consulte Obtener un mapa() para devolver una lista en Python 3.x para obtener respuestas más específicas sobre map.

Dick Lucas avatar Nov 30 '12 11:11 Dick Lucas
Aceptado

Puede leer sobre los cambios en Novedades de Python 3.0 . Debería leerlo detenidamente cuando pase de 2.xa 3.x, ya que se han cambiado muchas cosas.

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, por ejemplo list(map(...)), pero una mejor solución suele ser usar una lista por comprensión (especialmente cuando el código original usa lambda) o reescribir el código para que no necesite una lista en absoluto. map()Los efectos secundarios de la función son especialmente complicados ; la transformación correcta es utilizar un forbucle regular (ya que crear una lista sería un desperdicio).
  • [...]

Incorporaciones

  • [...]
  • Eliminado reduce(). Úselo functools.reduce()si realmente lo necesita; sin embargo, el 99 por ciento de las veces un forbucle explícito es más legible.
  • [...]
 avatar Nov 30 '2012 04:11

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

Entonces, para filtery map, puedes envolverlos list()para ver los resultados como lo hiciste 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 el uso de mapas y filtros con expresiones generadoras o listas por comprensión. 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 el 99 por ciento de las veces más fáciles de leer que de reducir, pero yo me quedaría con functools.reduce.

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

Joshua D. Boyd avatar Nov 30 '2012 04:11 Joshua D. Boyd

Como complemento 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 aquellas que devuelven una lista y las 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 parece a este:

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

Que imprime:

190
[1, 2]

Sólo mis 2 centavos :-)