lista kontra filtr lambda +
Okazało się, że mam podstawową potrzebę filtrowania: mam listę i muszę filtrować ją według atrybutu przedmiotów.
Mój kod wyglądał tak:
my_list = [x for x in my_list if x.attribute == value]
Ale wtedy pomyślałem, czy nie byłoby lepiej napisać to w ten sposób?
my_list = filter(lambda x: x.attribute == value, my_list)
Jest bardziej czytelny, a jeśli jest potrzebny do wykonania, lambda może zostać wyjęta, aby coś zyskać.
Pytanie brzmi: czy są jakieś zastrzeżenia w używaniu drugiego sposobu? Jakaś różnica w wydajności? Czy brakuje mi Pythona Way™ całkowicie i należy to zrobić w jeszcze inny sposób (np. używając itemgetter zamiast lambda)?
14 answers
To dziwne, jak wiele piękna różni się dla różnych ludzi. Uważam, że zrozumienie listy jest o wiele jaśniejsze niż filter
+lambda
, ale użyj tego, co znajdziesz łatwiejsze. Jednak przestań podawać nazwy zmiennych już używanych do wbudowanych, co jest mylące.
Są dwie rzeczy, które mogą spowolnić korzystanie z filter
.
Pierwszym jest wywołanie funkcji overhead: gdy tylko użyjesz funkcji Pythona (niezależnie od tego, czy została utworzona przez def
czy lambda
), prawdopodobnie filtr będzie wolniejszy niż rozumienie listy. To prawie na pewno nie wystarczy, aby mieć znaczenie i nie powinieneś myśleć zbyt wiele o wydajności, dopóki nie zmierzysz kodu i nie uznasz go za wąskie gardło, ale różnica będzie tam.
value
). Jest to wolniejsze niż dostęp do zmiennej lokalnej i w Pythonie 2.x zrozumienie listy ma dostęp tylko do zmiennych lokalnych. Jeśli używasz Pythona 3.x spis treści uruchamia się w oddzielnej funkcji, więc będzie również uzyskiwać dostęp value
poprzez zamknięcie i ta różnica nie będzie miała zastosowania.
Inną opcją do rozważenia jest użycie generatora zamiast rozumienia listy:
def filterbyvalue(seq, value):
for el in seq:
if el.attribute==value: yield el
Następnie w głównym kodzie (gdzie czytelność naprawdę ma znaczenie) zastąpiłeś zarówno rozumienie list, jak i filtr, mając nadzieję, że znaczącą nazwę funkcji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-06 13:22:44
Jest to nieco religijny problem w Pythonie. Chociaż Guido rozważał usunięcie map
, filter
i reduce
z Pythona 3, było wystarczająco dużo luzu, że w końcu tylko reduce
został przeniesiony z wbudowanych do functools.reduce .
Osobiście uważam, że składanie list jest łatwiejsze do odczytania. Bardziej jednoznaczne jest to, co dzieje się z wyrażeniem [i for i in list if i.attribute == value]
, ponieważ całe zachowanie znajduje się na powierzchni, a nie wewnątrz funkcji filtra.
Nie martwiłbym się zbyt wiele o różnicy wydajności między tymi dwoma podejściami, ponieważ jest marginalna. Naprawdę zoptymalizowałbym to tylko, gdyby okazało się, że jest to wąskie gardło w Twojej aplikacji, co jest mało prawdopodobne.
Również od BDFL wanted filter
gone from the language then surely that automatically makes list comprehensions more Pythonic; -)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-04 17:41:40
Ponieważ jakakolwiek różnica prędkości musi być znikoma, czy używać filtrów, czy składanie list sprowadza się do kwestii gustu. Ogólnie jestem skłonny do korzystania ze zrozumień (co wydaje się zgadzać z większością innych odpowiedzi tutaj), ale jest jeden przypadek, w którym wolę filter
.
Bardzo częstym przypadkiem użycia jest wyciąganie wartości pewnego iterowalnego x podlegającego predykatowi P (x):
[x for x in X if P(x)]
Ale czasami chcesz zastosować jakąś funkcję do wartości pierwszy:
[f(x) for x in X if P(f(x))]
Jako konkretny przykład rozważmy
primes_cubed = [x*x*x for x in range(1000) if prime(x)]
Myślę, że wygląda to nieco lepiej niż użycie filter
. Ale teraz rozważ
prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]
W tym przypadku chcemy filter
w stosunku do obliczonej wartości. Oprócz kwestii obliczenia sześcianu dwa razy (wyobraźmy sobie droższe obliczenia), jest kwestia napisania wyrażenia dwa razy, naruszając estetykę suchą . W tym przypadku byłbym skłonny użyć
prime_cubes = filter(prime, [x*x*x for x in range(1000)])
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-27 08:05:49
Chociaż filter
może być "szybszym sposobem", "sposobem Pythonicznym" byłoby nie dbanie o takie rzeczy, chyba że wydajność jest absolutnie krytyczna (w takim przypadku nie używałbyś Pythona!).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-10 10:22:36
Pomyślałem, że po prostu dodam, że w Pythonie 3, filter() jest w rzeczywistości obiektem iteratora, więc musisz przekazać wywołanie metody filtrowania DO list (), aby zbudować filtrowaną listę. Więc w Pythonie 2:
lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)
Listy b I c mają te same wartości i zostały uzupełnione mniej więcej w tym samym czasie, co filter() było równoważne [x Dla x W y, Jeśli z]. Jednak w 3 ten sam kod zostawiłby listę c zawierającą obiekt filtrujący, a nie filtrowaną listę. Aby uzyskać te same wartości w 3:
lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))
The problem polega na tym, że list() pobiera iterowalny argument i tworzy nową listę z tego argumentu. Rezultatem jest to, że użycie filtra w ten sposób w Pythonie 3 zajmuje nawet dwa razy więcej czasu niż metoda [X for x in y if Z], ponieważ trzeba iterację nad wyjściem z filter () jak również oryginalną listą.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-06 06:48:17
Ważną różnicą jest to, że zrozumienie listy zwróci list
, podczas gdy filtr zwróci filter
, którym nie można manipulować jak list
(np. wywołaj len
na nim, co nie działa z powrotem filter
).
Moja własna nauka doprowadziła mnie do podobnej kwestii.
To powiedziawszy, jeśli istnieje sposób, aby uzyskać wynik list
z filter
, trochę jak w.NET, gdy robisz lst.Where(i => i.something()).ToList()
, jestem ciekawy tego.
EDIT: tak jest w Pythonie 3, NIE 2 (patrz dyskusja w komentarzach).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-31 14:27:35
Uważam drugi sposób za bardziej czytelny. Mówi dokładnie, jaki jest zamiar: filtruj listę.
PS: nie używaj 'list' jako nazwy zmiennej
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-10 10:19:27
Filtr jest właśnie tym. Odfiltrowuje elementy listy. Możesz zobaczyć, że definicja wspomina o tym samym(w oficjalnym linku docs, o którym wspomniałem wcześniej). Natomiast zrozumienie listy jest czymś, co tworzy nową listę po zadziałaniu na coś na poprzedniej liście.(Zarówno filtrowanie, jak i rozumienie list tworzy nową listę i nie wykonuje operacji w miejsce starszej listy. Nowa lista jest czymś w rodzaju listy z, powiedzmy, zupełnie nowym typem danych. Jak nawracanie liczby całkowite Do string, itp.)
W twoim przykładzie lepiej jest użyć filtra niż rozumienia listy, zgodnie z definicją. Jeśli jednak chcesz, powiedzmy other_attribute z elementów listy, w twoim przykładzie ma być pobrany jako nowa lista, możesz użyć rozumienia listy.
return [item.other_attribute for item in my_list if item.attribute==value]
Tak właściwie pamiętam o zrozumieniu filtrów i list. Usuń kilka rzeczy z listy i zachowaj pozostałe elementy nienaruszone, użyj filtra. Użyj logiki na własną rękę przy elementach i Utwórz rozwiniętą listę odpowiednią do jakiegoś celu, użyj rozumienia listy.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-29 07:38:11
Ogólnie filter
jest nieco szybsze, jeśli używa wbudowanej funkcji.
Spodziewałbym się, że w Twoim przypadku zrozumienie listy będzie nieco szybsze
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-10 10:17:47
Oto krótki kawałek, którego używam, gdy muszę filtrować coś po zrozumieniu listy. Po prostu połączenie filtra, lambda i list (inaczej znane jako lojalność kota i czystość psa).
W tym przypadku czytam plik, usuwam puste linie, komentuję linie i wszystko po komentarzu na linii:
# Throw out blank lines and comments
with open('file.txt', 'r') as lines:
# From the inside out:
# [s.partition('#')[0].strip() for s in lines]... Throws out comments
# filter(lambda x: x!= '', [s.part... Filters out blank lines
# y for y in filter... Converts filter object to list
file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-28 19:31:41
Trochę czasu zajęło mi zapoznanie się z higher order functions
filter
oraz map
. Więc przyzwyczaiłem się do nich i naprawdę podobało mi się filter
, ponieważ było wyraźne, że filtruje przez zachowanie tego, co jest prawdziwe i czułem się fajnie, że znam niektóre functional programming
terminy.
Potem przeczytałem ten fragment (Księga Pythona):
funkcje map i filtrów są nadal wbudowane w Pythonie 3, ale od wprowadzenia list compensions i generator ex‐ presje, nie są tak ważne. Listcomp lub genexp wykonuje zadanie map i filtr połączony, ale jest bardziej czytelny.
I teraz myślę, po co zawracać sobie głowę pojęciem filter
/ map
jeśli można to osiągnąć za pomocą już szeroko rozpowszechnionych idiomów, takich jak składanie list. Ponadto maps
i filters
są rodzajami funkcji. W tym przypadku wolę używać Anonymous functions
lambda.
W końcu, tylko ze względu na to, aby go przetestować, zmierzyłem obie metody (map
i listComp
) i nie widziałem żadnej istotnej różnicy prędkości, która uzasadniałaby argumenty na ten temat.
from timeit import Timer
timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))
timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))
#Map: 166.95695265199174
#List Comprehension 177.97208347299602
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-28 00:27:01
Oprócz zaakceptowanej odpowiedzi, istnieje przypadek narożny, w którym należy użyć filtra zamiast rozumienia listy. Jeśli lista jest nieszkodliwa, nie można jej bezpośrednio przetworzyć ze zrozumieniem listy. Prawdziwym przykładem jest użycie pyodbc
do odczytu wyników z bazy danych. fetchAll()
wyniki z cursor
to lista nie do złamania. W tej sytuacji, aby bezpośrednio manipulować zwracanymi wynikami, należy użyć filtra:
cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db)
Jeśli użyjesz tutaj rozumienia listy, otrzymasz błąd:
TypeError: unhashable type: 'list'
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-13 19:55:39
Co ciekawe w Pythonie 3 widzę, że filtr działa szybciej niż składanie list.
Zawsze myślałem, że listy będą bardziej wydajne. Coś jak: [name for name in brand_names_db if name is not None] Wygenerowany bajt jest nieco lepszy.
>>> def f1(seq):
... return list(filter(None, seq))
>>> def f2(seq):
... return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2 0 LOAD_GLOBAL 0 (list)
2 LOAD_GLOBAL 1 (filter)
4 LOAD_CONST 0 (None)
6 LOAD_FAST 0 (seq)
8 CALL_FUNCTION 2
10 CALL_FUNCTION 1
12 RETURN_VALUE
>>> disassemble(f2.__code__)
2 0 LOAD_CONST 1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
2 LOAD_CONST 2 ('f2.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_FAST 0 (seq)
8 GET_ITER
10 CALL_FUNCTION 1
12 RETURN_VALUE
Ale faktycznie są wolniejsze:
>>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
21.177661532000116
>>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
42.233950221000214
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 19:13:25
Moje ujęcie
def filter_list(list, key, value, limit=None):
return [i for i in list if i[key] == value][:limit]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-21 22:54:53