Как мне эффективно фильтровать вычисляемые значения в рамках понимания списка Python?
-
02-07-2019 - |
Вопрос
Синтаксис понимания списка Python позволяет легко фильтровать значения в рамках понимания.Например:
result = [x**2 for x in mylist if type(x) is int]
Вернет список квадратов целых чисел в mylist.Однако, что, если тест включает в себя некоторые (дорогостоящие) вычисления, и вы хотите отфильтровать результат?Одним из вариантов является:
result = [expensive(x) for x in mylist if expensive(x)]
Это приведет к получению списка значений expensive(x), отличных от "false", однако функция expensive() вызывается дважды для каждого x.Существует ли синтаксис понимания, который позволяет вам выполнить этот тест, вызывая expensive только один раз за x ?
Решение
Если вычисления уже хорошо объединены в функции, как насчет использования filter
и map
?
result = filter (None, map (expensive, mylist))
Вы можете использовать itertools.imap
если список очень большой.
Другие советы
После минутного раздумья я пришел к своему собственному ответу.Это может быть сделано с помощью вложенных представлений:
result = [y for y in (expensive(x) for x in mylist) if y]
Я предполагаю, что это работает, хотя я нахожу, что вложенные понимания читабельны лишь незначительно
Самый очевидный (и, я бы сказал, наиболее читаемый) ответ - использовать не выражение для понимания списка или генератора, а скорее реальный генератор:
def gen_expensive(mylist):
for item in mylist:
result = expensive(item)
if result:
yield result
Это занимает больше места по горизонтали, но гораздо легче увидеть, что это делает с первого взгляда, и в конечном итоге вы не повторяетесь.
result = [x for x in map(expensive,mylist) if x]
map() вернет список значений каждого объекта в mylist, переданный в expensive().Затем вы можете перечислить это и отбросить ненужные значения.
Это чем-то похоже на вложенное понимание, но должно быть быстрее (поскольку интерпретатор python может оптимизировать его довольно легко).
Это именно то, с чем генераторы подходят для работы:
result = (expensive(x) for x in mylist)
result = (do_something(x) for x in result if some_condition(x))
...
result = [x for x in result if x] # finally, a list
- Это делает абсолютно понятным, что происходит на каждом этапе конвейера.
- Явное преобладает над неявным
- Использует генераторы везде до последнего шага, поэтому никаких больших промежуточных списков
см .: "Генераторные трюки для системных программистов" Дэвида Бизли
Ты всегда мог бы запоминать тот самый expensive()
функция, так что вызов ее во второй раз - это просто поиск вычисленного значения x
.
Вот лишь одна из многих реализаций memoize в качестве декоратора.
Вы могли бы запомнить дорогой (x) (и если вы часто вызываете дорогой (x), вам, вероятно, следует запомнить его любым способом.На этой странице представлена реализация memoize для python:
http://code.activestate.com/recipes/52201/
Это имеет дополнительное преимущество в том, что может быть запущен expensive(x) Меньше более N раз, поскольку любые повторяющиеся записи будут использовать памятку из предыдущего выполнения.
Обратите внимание, что при этом предполагается, что expensive (x) является истинной функцией и не зависит от внешнего состояния, которое может измениться.Если дорого (x) действительно зависит от внешнего состояния, и вы можете определить, когда это состояние изменяется, или вы это знаете обыкновение внесите изменения во время понимания списка, после чего вы сможете сбросить заметки до начала понимания.
Я буду отдавать предпочтение:
itertools.ifilter(bool, (expensive(x) for x in mylist))
Это имеет то преимущество, что:
- избегайте None как функции (будет исключена в Python 3): http://bugs.python.org/issue2186
- используйте только итераторы.
Существует простое старое использование a for
цикл для добавления в список тоже есть:
result = []
for x in mylist:
expense = expensive(x)
if expense:
result.append(expense)