Zanurkuj w Pythonie/Programowanie funkcyjne/Filtrowanie listy
Jeszcze o filtrowaniu list
[edytuj]Zapoznaliście się już z filtrowaniem list przy użyciu wyrażeń listowych (ang. list comprehension). Istnieje jeszcze jeden sposób na osiągnięcie tego celu, przez wiele osób uznawany za bardziej wyrazisty.
W języku Python istnieje wbudowana funkcja filtrująca (filter) przyjmująca dwa parametry, funkcję oraz listę, i zwracająca listę[1]. Funkcja przekazana jako pierwszy argument do funkcji filter musi przyjmować jeden argument, natomiast lista zwrócona przez funkcję filtrującą będzie zawierała te elementy z listy przekazanej do funkcji filtującej, dla których funkcja przekazana w pierwszym argumencie zwróciła wartość true.
Czy wszystko jasne? To nie takie trudne, jak się wydaje.
Przykład 16.7. Wprowadzenie do funkcji filter
>>> def odd(n): #(1) ... return n % 2 ... >>> li = [1, 2, 3, 5, 9, 10, 256, -3] >>> filter(odd, li) #(2) [1, 3, 5, 9, -3] >>> [e for e in li if odd(e)] #(3) [1, 3, 5, 9, -3] >>> filteredList = [] >>> for n in li: #(4) ... if odd(n): ... filteredList.append(n) ... >>> filteredList [1, 3, 5, 9, -3]
- Funkcja odd zwraca True jeśli n jest nieparzyste a False w przeciwnym przypadku; używa do tego wbudowanej funkcji modulo ("%").
- Funkcja filter przyjmuje dwa argumenty: funkcję odd oraz listę li. filter iteruje po liście i dla każdego jej elementu wywołuje odd. Jeśli odd zwróci wartość true (pamiętajcie, że każda niezerowa wartość ma w języku Python logiczną wartość true), wówczas element jest dodawany do listy wynikowej, w przeciwnym przypadku jest on pomijany. W rezultacie otrzymujemy listę nieparzystych elementów z listy oryginalnej, w takiej samej kolejności, w jakiej elementy pojawiały się na oryginalnej liście.
- Jak widzieliśmy w podrozdziale 4.5 Filtrowanie listy, ten sam cel można osiągnąć używając wyrażeń listowych.
- Można również użyć pętli. W zależności od tego, jakim doświadczeniem programistycznym dysponujecie, ten sposób może się wam wydawać bardziej "bezpośredni", jednak użycie funkcji takich jak filter jest znacznie bardziej wyraziste. Nie tylko jest prostsze w zapisie, jest również łatwiejsze w czytaniu. Czytanie kodu pętli przypomina patrzenie na obraz ze zbyt małej odległości; widzi się detale, jednak potrzeba kilku chwil, by móc odsunąć się na tyle, aby dojrzeć całe dzieło: "Och, to tylko filtrowanie listy!".
Przykład 16.8. Funkcja filter w regression.py
files = os.listdir(path) #(1)
test = re.compile("test\.py$", re.IGNORECASE) #(2)
files = filter(test.search, files) #(3)
- Jak widzieliśmy w podrozdziale 16.2 Znajdowanie ścieżki, ścieżka (path) może zawierać pełną lub częściową nazwę ścieżki do katalogu, w którym znajduje się właśnie wykonywany skrypt lub może zawierać pusty napis, jeśli skrypt został uruchomiony z bieżącego katalogu. Jakkolwiek by nie było, na liście files znajdą się nazwy plików z tego samego katalogu, w którym znajduje się uruchomiony skrypt.
- Jest to skompilowane wyrażenie regularne. Jak widzieliśmy w podrozdziale 15.3 Refaktoryzacja, jeśli to samo wyrażenie ma być używane więcej niż raz, warto je skompilować w celu poprawienia wydajności programu. Obiekt powstały w wyniku kompilacji posiada metodę search, która pobiera jeden argument: napis, do którego powinno się dopasować wyrażenie regularne. Jeśli dopasowanie nastąpi, metoda search zwróci obiekt klasy Match zawierający informację o sposobie dopasowania wyrażenia regularnego; w przeciwnym przypadku zwróci None, czyli zdefiniowaną w języku Python wartość null.
- Metoda search powinna zostać wywołana na obiekcie skompilowanego wyrażenia regularnego dla każdego elementu na liście files. Jeśli wyrażenie zostanie dopasowane do elementu, metoda ta zwróci obiekt klasy Match, który ma wartość logiczną true, a więc element zostanie dołączony do listy wynikowej zwróconej przez filtr. Jeśli wyrażenie nie zostanie dopasowane, metoda search zwróci wartość None, która ma wartość logiczną false, a więc dopasowywany element nie zostanie dołączony do listy wynikowej.
Notatka historyczna. Wersje języka Python wcześniejsze niż 2.0 nie obsługiwały jeszcze wyrażeń listowych, a więc nie można było ich użyć w celu przefiltrowania listy; istniała jednak funkcja filter. Nawet po wprowadzeniu wyrażeń listowych w 2.0 niektóre osoby wciąż wolą używać starej metody filtrującej filter (oraz towarzyszącej jej funkcji map, o której powiem jeszcze w tym rozdziale). W chwili obecnej działają obydwie te techniki, a wybór jednej z nich jest po prostu kwestią stylu. Toczy się dyskusja, czy map i filter nie powinny zostać "przedawnione" w przyszłych wersjach języka, jednak dotychczas ostateczna decyzja nie zapadła. |
Przykład 16.9. Filtrowanie przy użyciu wyrażeń listowych
files = os.listdir(path)
test = re.compile("test\.py$", re.IGNORECASE)
files = [f for f in files if test.search(f)] #(1)
- Wykonanie tej linii kodu będzie miało dokładnie ten sam efekt, co użycie funkcji filtrującej. Który sposób jest bardziej ekspresyjny? Wszystko zależy od was.
Przypisy
- ↑ Patrząc z technicznego punktu widzenia, drugim argumentem funkcji filter może być dowolna sekwencja, włączając w to listy, krotki oraz klasy, które funkcjonują jak listy, ponieważ mają zdefiniowaną metodę specjalną __getitem__. Jeśli to możliwe, funkcja filter zwraca ten sam typ danych, który otrzymała, a więc wynikiem filtrowania listy będzie lista, a wynikiem filtrowania krotki będzie krotka. Uwagi te dotyczą również funkcji map, o której będzie mowa w następnym podrozdziale.