medium - python list find lambda




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

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

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

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


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


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

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

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

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


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

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

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

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

Затем я прочитал этот отрывок (Fluent Python Book):

Функции карт и фильтров по-прежнему встроены в Python 3, но с момента введения перечня списков и выражений генератора они не так важны. 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

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

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

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


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

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

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

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


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


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

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


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

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

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

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







lambda