список по сравнению с lambda + filter

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

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

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

Ответ 1

Странно, сколько красоты у разных людей. Я считаю, что понимание списка намного яснее, чем filter + lambda, но используйте то, что вам легче.

Есть две вещи, которые могут замедлить ваше использование 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

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

Ответ 2

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

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

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

Я думал, что просто добавлю, что в python 3 фильтр() на самом деле является объектом итератора, поэтому вам нужно передать вызов метода фильтра в список(), чтобы создать отфильтрованный список. Итак, в 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 имеют одинаковые значения и были выполнены примерно в то же время, что и фильтр() был эквивалентен [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 for x in y if z], потому что вам нужно перебирать результат из фильтра(), а также в исходный список.

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

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

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

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

# 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])]

Ответ 12

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

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

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

И теперь я думаю, зачем беспокоиться о концепции 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

Ответ 13

Одно из преимуществ лямбда-версии состоит в том, что вы можете захватывать дополнительные переменные, если ваше условие зависит от них:

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

потому что

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

вернет name 'value' is not defined, потому что значение переменной недоступно в условии.

Обе версии отлично работают с проверкой на varaibale.

Ответ 14

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

Я всегда думал, что понимание списка будет более производительным. Что-то вроде: [имя для имени в brand_names_db, если имя не 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

Ответ 15

Мой прием

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]