it-swarm.com.ru

понимание списка против лямбда + фильтр

Я обнаружил, что у меня есть базовая потребность в фильтрации: у меня есть список, и я должен отфильтровать его по атрибуту элементов.

Мой код выглядел так:

my_list = [x for x in my_list if x.attribute == value]

Но потом я подумал: не лучше ли написать это так?

my_list = filter(lambda x: x.attribute == value, my_list)

Это более читабельно, и если нужно для производительности, лямбда может быть извлечена, чтобы получить что-то. 

Вопрос: есть ли какие-то предостережения при использовании второго способа? Есть разница в производительности? Я полностью пропускаю Pythonic Way ™ и должен сделать это еще одним способом (например, использовать itemgetter вместо лямбды)?

703
Agos

Странно, сколько красоты у разных людей. Я считаю, что понимание списка намного яснее, чем filter + lambda, но используйте то, что вам проще. Однако, прекратите давать имена переменных, которые уже используются для встроенных модулей, это сбивает с толку. [ Вопрос изначально использовал list в качестве имени переменной, но был обновлен в ответ на этот ответ. ]

Есть две вещи, которые могут замедлить использование filter.

Первый - это накладные расходы при вызове функции: как только вы используете функцию Python (независимо от того, создана ли она def или lambda), вполне вероятно, что фильтр будет медленнее, чем понимание списка. Это почти наверняка недостаточно для того, чтобы иметь значение, и вы не должны много думать о производительности, пока не рассчитаете свой код и не обнаружите, что это узкое место, но разница будет.

Другие накладные расходы, которые могут возникнуть, это то, что лямбда вынуждена обращаться к переменной области действия (value). Это медленнее, чем доступ к локальной переменной, и в Python 2.x понимание списка доступно только для локальных переменных. Если вы используете Python 3.x, постижение списка выполняется в отдельной функции, поэтому он также будет обращаться к value через замыкание, и это различие не будет применяться.

Другой вариант, который следует рассмотреть, - использовать генератор вместо понимания списка:

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

Затем в вашем основном коде (где читаемость действительно имеет значение) вы заменили как понимание списка, так и фильтр на многообещающее имя функции.

486
Duncan

Это несколько религиозная проблема в Python. Несмотря на то, что Гвидо рассматривал возможность удаления map, filter и reduce из Python 3, было достаточно обратной реакции, что в итоге только reduce была перемещена из встроенных в functools.reduce .

Лично я нахожу список понимания легче читать. Более ясно, что происходит с выражением [i for i in list if i.attribute == value], поскольку все поведение находится на поверхности, а не внутри функции фильтра.

Я бы не стал сильно беспокоиться о разнице в производительности между двумя подходами, поскольку она незначительна. Я бы действительно оптимизировал это, только если это оказалось узким местом в вашем приложении, что маловероятно.

Кроме того, поскольку BDFL хотел, чтобы filter ушел из языка, тогда, конечно, это автоматически делает списки более Pythonic ;-)

201
Tendayi Mawushe

Поскольку любая разница в скорости обязательно будет крошечной, использование фильтров или списочных представлений зависит от вкуса. В целом, я склонен использовать понимание (что, похоже, согласуется с большинством других ответов здесь), но есть один случай, когда я предпочитаю filter

Очень частый вариант использования - извлечение значений некоторого итерируемого X с использованием предиката P (x):

[x for x in X if P(x)]

но иногда вы хотите сначала применить некоторую функцию к значениям:

[f(x) for x in X if P(f(x))]


В качестве конкретного примера рассмотрим

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

Я думаю, что это выглядит немного лучше, чем использование filter. Но теперь посмотрим

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

В этом случае мы хотим filter против пост-вычисленного значения. Помимо проблемы вычисления куба дважды (представьте себе более дорогие вычисления), существует проблема написания выражения дважды, нарушая DRY эстетику. В этом случае я был бы склонен использовать

prime_cubes = filter(prime, [x*x*x for x in range(1000)])
60
I. J. Kennedy

Хотя filter может быть «более быстрым способом», «Pythonic way» будет не заботиться о таких вещах, если производительность не является абсолютно критической (в этом случае вы не будете использовать Python!).

27
Umang

Я подумал, что просто добавлю, что в python 3 filter () на самом деле является объектом итератора, поэтому вам нужно передать вызов метода filter в list (), чтобы построить отфильтрованный список. Итак, в Python 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)

списки b и c имеют одинаковые значения и были заполнены примерно в то же время, что filter () был эквивалентен [x для x в y, если z]. Однако в 3 этот же код оставил бы список c, содержащий объект фильтра, а не отфильтрованный список. Чтобы получить те же значения в 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))

Проблема в том, что list () принимает в качестве аргумента итерацию и создает новый список из этого аргумента. В результате использование фильтра в Python 3 таким способом занимает вдвое больше времени, чем метод [x для x в y, если z], потому что вам приходится перебирать выходные данные filter (), а также исходный список. 

15
Jim50

Важным отличием является то, что понимание списка вернет list, а фильтр возвращает filter, с которым вы не можете манипулировать, как list (то есть: вызовите len для него, который не работает с возвратом filter).

Мое самообучение привело меня к некоторой аналогичной проблеме.

Тем не менее, если есть способ получить результирующую переменную list из переменной filter, немного похожую на ту, которую вы сделали бы в .NET при выполнении функции lst.Where(i => i.something()).ToList(), мне любопытно узнать это.

Правка: Это относится к Python 3, а не 2 (см. Обсуждение в комментариях).

10
Adeynack

Я считаю второй способ более читабельным. Он точно скажет вам, что вы хотите: отфильтруйте список.
PS: не используйте «список» в качестве имени переменной

9
unbeli

Фильтр это просто так. Он отфильтровывает элементы списка. Вы можете видеть, что определение упоминает то же самое (в официальной ссылке на документы, которую я упоминал ранее). Принимая во внимание, что понимание списка - это то, что создает новый список после воздействия на что-то в предыдущем списке. (И фильтрация, и понимание списка создают новый список и не выполняют операции вместо старого списка. Новый список здесь - это нечто как список с, скажем, совершенно новым типом данных. Как преобразование целых чисел в строку и т. д.)

В вашем примере лучше использовать фильтр, чем понимание списка, согласно определению. Однако, если вы хотите, скажем, other_attribute из элементов списка, в вашем примере вы должны получить новый список, тогда вы можете использовать понимание списка.

return [item.other_attribute for item in my_list if item.attribute==value]

Вот как я на самом деле помню о фильтрах и списках. Удалите несколько вещей из списка и оставьте остальные элементы нетронутыми, используйте фильтр. Используйте некоторую логику самостоятельно для элементов и создайте разбавленный список, подходящий для какой-либо цели, используйте понимание списка.

7
thiruvenkadam

обычно filter немного быстрее при использовании встроенной функции.

Я ожидаю, что понимание списка будет немного быстрее в вашем случае 

6
John La Rooy

Вот небольшой фрагмент, который я использую, когда мне нужно что-то фильтровать после понимание списка. Просто комбинация фильтра, лямбды и списков (иначе называемых верностью кошки и чистотой собаки).

В этом случае я читаю файл, удаляя пустые строки, закомментированные строки и все, что угодно после комментария к строке:

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

В дополнение к принятому ответу, есть угловой случай, когда вы должны использовать фильтр вместо понимания списка. Если список не подлежит изменению, вы не можете напрямую обработать его с помощью понимания списка. Пример из реальной жизни - если вы используете pyodbc для чтения результатов из базы данных. fetchAll() - результат cursor - список, который невозможно изменить. В этой ситуации, чтобы напрямую манипулировать возвращаемыми результатами, следует использовать фильтр:

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) 

Если вы используете здесь понимание списка, вы получите ошибку:

TypeError: unhashable тип: 'список'

4
C.W.praen

Мне потребовалось некоторое время, чтобы ознакомиться с higher order functionsfilter и map. Так что я привык к ним, и мне действительно понравился filter, так как было ясно, что он фильтрует, сохраняя все правдивое, и я чувствовал себя классно, когда знал некоторые термины functional programming

Затем я прочитал этот отрывок (Свободная Книга Питона): 

Функции карт и фильтров по-прежнему встроены в Python 3, но с момента введения списочных представлений и генератора ex-__. давления, они не так важны. Listcomp или genexp выполняет работу map и Фильтр комбинированный, но более читаемый. 

И теперь я думаю, зачем беспокоиться о концепции filter/map, если вы можете достичь этого с помощью широко распространенных идиом, таких как списочные выражения. Кроме того, maps и filters являются разновидностями функций. В этом случае я предпочитаю использовать Anonymous functions lambdas. 

Наконец, просто для того, чтобы протестировать его, я рассчитал оба метода (map и listComp), и я не увидел какой-либо существенной разницы в скорости, которая бы оправдывала споры об этом. 

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
user1767754

Любопытно, что на Python 3 фильтр работает быстрее, чем списки.

Я всегда думал, что составления списка будут более производительными . Что-то вроде: [Имя для name в brand_names_db, если name не None] Сгенерированный байт-код немного лучше.

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

Но они на самом деле медленнее:

   >>> 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
0
Rod Senra