понимание списка против лямбда + фильтр - PullRequest
740 голосов
/ 10 июня 2010

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

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

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 вместо лямбды)?

Ответы [ 14 ]

510 голосов
/ 10 июня 2010

Странно, сколько красоты у разных людей. Я считаю, что понимание списка намного яснее, чем 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

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

205 голосов
/ 10 июня 2010

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

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

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

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

65 голосов
/ 13 ноября 2014

Поскольку любая разница в скорости обязательно будет крошечной, использовать фильтры или списки можно только по вкусу. В целом, я склонен использовать понимание (что, похоже, согласуется с большинством других ответов здесь), но есть один случай, когда я предпочитаю 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)])
28 голосов
/ 10 июня 2010

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

16 голосов
/ 06 сентября 2016

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

10 голосов
/ 16 октября 2014

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

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

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

РЕДАКТИРОВАТЬ: Это относится к Python 3, а не 2 (см. Обсуждение в комментариях).

9 голосов
/ 10 июня 2010

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

7 голосов
/ 29 января 2015

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

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

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

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

6 голосов
/ 10 июня 2010

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

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

5 голосов
/ 01 марта 2018

В дополнение к принятому ответу, есть угловой случай, когда вы должны использовать фильтр вместо понимания списка. Если список не подлежит изменению, вы не можете напрямую обработать его с помощью понимания списка. Пример из реальной жизни - если вы используете 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 тип: 'list'

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...