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

Author: ivanleoncz, 2010-06-10

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.

Lambda jest zmuszona do dostępu do zmiennej o zasięgu (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.

 437
Author: Duncan,
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; -)

 189
Author: Tendayi Mawushe,
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)])
 53
Author: I. J. Kennedy,
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!).

 25
Author: Umang,
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ą.

 14
Author: Jim50,
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).

 9
Author: Adeynack,
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

 8
Author: unbeli,
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.

 7
Author: thiruvenkadam,
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

 5
Author: John La Rooy,
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])]
 5
Author: rharder,
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
 3
Author: user1767754,
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'

 3
Author: C.W.praen,
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
 1
Author: Rod Senra,
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]
 -5
Author: tim,
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