Сравнение: генераторы списков против lambda + filter
У меня есть список, который я хочу отфильтровать по атрибуту элементов. Какой из следующих вариантов является более предпочтительным с точки зрения читаемости, производительности или по другим причинам?
xs = [x for x in xs if x.attribute == value]
или
xs = filter(lambda x: x.attribute == value, xs)
Какой способ лучше использовать в разных ситуациях?
5 ответ(ов)
Это странно, как восприятие красоты варьируется у разных людей. Я нахожу списковые включения гораздо более понятными, чем filter
+ lambda
, но выбирайте тот вариант, который вам удобнее.
Есть две вещи, которые могут замедлить использование filter
.
Первая — это накладные расходы на вызовы функций. Как только вы используете функцию на Python (будь то созданная с помощью def
или lambda
), с большой долей вероятности filter
будет медленнее, чем списковое включение. Хотя это почти наверняка не имеет значения, и вам не стоит беспокоиться о производительности, пока вы не замерили свое время выполнения и не обнаружили узкое место, тем не менее разница будет заметна.
Вторая проблема, которую следует учесть, — это то, что лямбда-функция вынуждена обращаться к переменной из области видимости (value
). Это медленнее, чем доступ к локальной переменной, и в Python 2.x списковое включение обращается только к локальным переменным. Если вы используете Python 3.x, то списковое включение выполняется в отдельной функции, поэтому оно также будет обращаться к value
через замыкание, и эта разница не будет актуальна.
Еще один вариант — рассмотреть использование генератора вместо спискового включения:
def filterbyvalue(seq, value):
for el in seq:
if el.attribute == value:
yield el
Таким образом, в вашем основном коде (где действительно важна читаемость) вы заменили и списковое включение, и filter
на, надеюсь, осмысленное имя функции.
Хотя filter
может быть "быстрее", "питоническим" способом было бы не беспокоиться о таких вещах, если производительность не является абсолютно критичной (в таком случае вы бы, скорее всего, не использовали Python!).
Я бы хотел добавить, что в Python 3 функция filter()
возвращает объект-итератор, поэтому, чтобы получить отфильтрованный список, вам нужно передать результат вызова filter()
в функцию list()
. В Python 2 код будет выглядеть так:
lst_a = range(25) # произвольный список
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)
Списки lst_b
и lst_c
будут иметь одинаковые значения и будут созданы примерно за одно и то же время, так как filter()
эквивалентен [x for x in y if z]
. Однако в Python 3 этот же код оставит lst_c
как объект filter
, а не как отфильтрованный список. Чтобы получить такие же значения в Python 3, используйте:
lst_a = range(25) # произвольный список
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num % 2 == 0, lst_a))
Проблема заключается в том, что list()
принимает в качестве аргумента итерируемый объект и создает на его основе новый список. В результате использование filter()
в таком виде в Python 3 может занимать в два раза больше времени, чем метод [x for x in y if z]
, поскольку необходимо итерироваться как по выходным данным из filter()
, так и по оригинальному списку.
Важное отличие заключается в том, что генератор списков (list comprehension) возвращает list
, в то время как функция filter
возвращает объект filter
, который нельзя использовать так же, как список (например, на него нельзя вызвать len
, что не работает с результатом filter
).
В процессе самообучения я столкнулся с похожей проблемой.
С учетом вышесказанного, если есть способ получить результат в виде list
из filter
, как это делается в .NET с помощью lst.Where(i => i.something()).ToList()
, мне было бы интересно это узнать.
ПРИМЕЧАНИЕ: это относится к Python 3, а не к 2 (см. обсуждение в комментариях).
Я считаю второй способ более читаемым. Он четко показывает намерение: отфильтровать коллекцию.
P.S.: Не используйте 'list' в качестве имени переменной.
Как получить последний элемент списка?
Как клонировать список, чтобы он не изменялся неожиданно после присваивания?
В чем разница между методами append и extend для списков в Python?
Самый быстрый способ проверить наличие значения в списке
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе