Jak używać filtru, mapy i redukcji w Pythonie 3

321

filter, mapi reducedziałają doskonale w Pythonie 2. Oto przykład:

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

Ale w Python 3 otrzymuję następujące dane wyjściowe:

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

Byłbym wdzięczny, gdyby ktoś mógł mi wyjaśnić, dlaczego tak jest.

Zrzut ekranu kodu dla większej przejrzystości:

Sesje IDLE Pythona 2 i 3 obok siebie

Dick Lucas
źródło
1
Krótko mówiąc, lista nie jest jedynym typem danych. Jeśli chcesz listę, powiedz, że chcesz listę. Ale w większości przypadków i tak chcesz czegoś innego.
Veky,

Odpowiedzi:

346

Możesz przeczytać o zmianach w Co nowego w Python 3.0 . Powinieneś przeczytać go dokładnie, kiedy przechodzisz z wersji 2.x na 3.x, ponieważ wiele zostało zmienionych.

Cała odpowiedź tutaj to cytaty z dokumentacji.

Widoki i iteratory zamiast list

Niektóre dobrze znane interfejsy API nie zwracają już list:

  • [...]
  • map()i filter()zwróć iteratory. Jeśli naprawdę potrzebujesz listy, szybka poprawka jest np. list(map(...)), Ale lepszym rozwiązaniem jest często użycie rozumienia listy (szczególnie, gdy oryginalny kod używa lambda) lub przepisanie kodu, aby w ogóle nie potrzebował listy. Szczególnie trudne jest map()wywoływanie efektów ubocznych funkcji; poprawną transformacją jest użycie zwykłej forpętli (ponieważ tworzenie listy byłoby po prostu marnotrawstwem).
  • [...]

Wbudowane

  • [...]
  • Usunięte reduce(). Użyj, functools.reduce()jeśli naprawdę tego potrzebujesz; jednak 99 procent czasu wyraźnej forpętli jest bardziej czytelna.
  • [...]
nhahtdh
źródło
21
Dodanie list(map(...) wszędzie ... jak na świecie pomaga czytelność ... pythonnie może poradzić sobie z progresywną / streamingową aplikacją funkcjonalnych kombinacji. Inne języki Mogę połączyć kilkanaście operacji z kolekcją z rzędu i jest to czytelne. Tutaj? co chcesz - kilkanaście sposobów zagnieżdżonych in?
javadba
11
Jeśli pracujesz w trybie rozkazującym, prawdopodobnie bardziej czytelną opcją jest pętla for. Ale istnieją dobre powody, by preferować kontekst funkcjonalny - a odejście od tego, aby wrócić do procedur, może być naprawdę brzydkie.
MatrixManAtYrService
2
@javadba Czy jesteś pewien, że w „aplikacji do przesyłania strumieniowego” listw ogóle musisz dodać połączenie? Pomyślałem, że znaczenie „streaming” jest takie, że „żadna lista nie jest tworzona; przed przejściem do następnego przetworz każdy element wejścia całkowicie”.
Imperishable Night,
@MatrixManAtYrService Jeśli masz pewność, że zachowanie Python 2 jest tym, czego potrzebujesz, zawsze możesz przedefiniować map.
Imperishable Night,
6
Nadal nie rozumiem, jak argument o czytelności prowadzi do takiej zmiany. Gdyby to z powodów wydajnościowych, mógłbym zrozumieć ...
Minato,
86

Funkcjonalność mapi filterzostała celowo zmieniona, aby zwrócić iteratory, a redukcja została usunięta z wbudowanego i umieszczona w functools.reduce.

Tak więc, na filteri map, można owinąć je list(), aby zobaczyć wyniki jak przedtem.

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

Zaleca się teraz, aby zastąpić korzystanie z mapy i filtru wyrażeniami generatora lub listami. Przykład:

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

Mówi się, że dla pętli 99% czasu jest łatwiejszych do odczytania niż zmniejszania, ale po prostu bym się trzymał functools.reduce.

Edycja : Liczba 99 procent jest pobierana bezpośrednio ze strony What's New In Python 3.0 autorstwa Guido van Rossuma.

Joshua D. Boyd
źródło
5
Nie trzeba tworzyć dodatkowych funkcji w opisach list. Wystarczy użyć[i*i*i for i in range(1,11)]
Xiao
2
Masz absolutną rację. Zachowałem tę funkcję na przykładach ze zrozumieniem listy, aby wyglądała podobnie do przykładów filtrów / map.
Joshua D. Boyd
5
i ** 3 jest również równoważne z i * i * i
Breezer
5
@Breezer faktycznie i**3zadzwoni i.__pow__(3)i i*i*i i.__mul__(i).__mul__(i)(lub coś takiego). Z ints to nie ma znaczenia, ale z liczbami liczbowymi / niestandardowymi klasami może nawet dawać różne wyniki.
synonim
1
Zauważyłem, że ilekroć słyszymy, że „Guido podjął decyzję X”, ten ból jest prawdopodobnym rezultatem. To świetny przykład: list(list(list(.. )))robić to, co było już pełne w Pythonie.
javadba
12

Jako dodatek do innych odpowiedzi brzmi to jak dobry przypadek użycia dla menedżera kontekstu, który odwzoruje nazwy tych funkcji na te, które zwracają listę i wprowadzają reducew globalnej przestrzeni nazw.

Szybka implementacja może wyglądać następująco:

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

Przy użyciu, które wygląda tak:

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

Które wydruki:

190
[1, 2]

Tylko moje 2 centy :-)

Dimitris Fasarakis Hilliard
źródło
1
pythonjako język jest bałagan - ale ma v dobre i bardzo dobre biblioteki: numpy, pandas, statsmodelsi przyjaciół .. I zostały buliding bibliotek wygodę jak pokazać tutaj, aby zmniejszyć ból w języku ojczystym - ale straciły energię, a nie próbować zboczyć daleko od data.frame/ datatablelub xarray. Ale chwała za próbę ...
javadba
7

Ponieważ reducemetoda została usunięta z wbudowanej funkcji z Python3, nie zapomnij zaimportować do functoolsswojego kodu. Zobacz fragment kodu poniżej.

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
źródło
2

Oto przykłady funkcji Filtruj, mapuj i zmniejszaj.

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

//Filtr

oddNumbers = lista (filtr (lambda x: x% 2! = 0, liczby))

print (nieparzyste numery)

//Mapa

multiplyOf2 = lista (mapa (lambda x: x * 2, liczby))

drukuj (multiplyOf2)

//Redukować

Funkcja zmniejszania, ponieważ nie jest powszechnie używana, została usunięta z wbudowanych funkcji w Pythonie 3. Jest ona nadal dostępna w module funkools, więc możesz:

z importu funkools zmniejsz

sumOfNumbers = zmniejsz (lambda x, y: x + y, liczby)

print (sumOfNumbers)

Yogendra Singh
źródło
0

Jedną z zalet mapowania, filtrowania i zmniejszania jest to, jak stają się one czytelne, gdy „połączymy” je razem, aby zrobić coś złożonego. Jednak wbudowana składnia nie jest czytelna i jest „wsteczna”. Sugeruję więc użycie PyFunctionalpakietu ( https://pypi.org/project/PyFunctional/ ). Oto porównanie tych dwóch:

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

Wersja PyFunkcjonalna

Bardzo czytelna składnia. Możesz powiedzieć:

„Mam sekwencję miejsc docelowych lotów. Z tego chcę uzyskać klucz dict, jeśli miasto ma wartości dict. Na koniec odfiltruj puste listy, które utworzyłem w tym procesie”.

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

Domyślna wersja Python

To wszystko do tyłu. Musisz powiedzieć:

„OK, więc jest lista. Chcę odfiltrować z niej puste listy. Dlaczego? Ponieważ pierwszy raz dostałem klucz dict, jeśli miasto było w wartościach dict. Och, lista, którą to robię, to lot_destynacje_dykta. „

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
źródło