Ответ 1
Если вычисления уже хорошо связаны с функциями, как насчет использования filter
и map
?
result = filter (None, map (expensive, mylist))
Вы можете использовать itertools.imap
, если список очень большой.
Синтаксис понимания списка Python позволяет легко фильтровать значения в понимании. Например:
result = [x**2 for x in mylist if type(x) is int]
Вернет список квадратов целых чисел в mylist. Однако, что, если тест включает некоторые (дорогостоящие) вычисления и вы хотите отфильтровать результат? Один из вариантов:
result = [expensive(x) for x in mylist if expensive(x)]
Это приведет к списку не "ложных" дорогих (x) значений, однако дорогостоящий() вызывается дважды для каждого x. Есть ли синтаксис понимания, который позволяет вам выполнять этот тест, только когда он дорого стоит один раз за 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, переданном дорогостоящему(). Затем вы можете перечислить-понять это и отказаться от ненужных значений.
Это похоже на вложенное понимание, но должно быть быстрее (поскольку интерпретатор 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
cf: "Трюки генератора для системных программистов" Дэвида Бизли
Вы всегда можете memoize функцию expensive()
, так что вызов ее во второй раз - это просто поиск рассчитанного значения x
.
Вот только одна из многих реализаций memoize в качестве декоратора.
Вы можете memoize дорогостоящий (x) (и если вы часто звоните в дорогостоящий (x), вы, вероятно, должны его memoize каким-либо образом. Эта страница дает реализацию memoize для python:
http://code.activestate.com/recipes/52201/
Это имеет дополнительное преимущество, при котором дорогостоящий (x) может быть запущен меньше N раз, поскольку любые повторяющиеся записи будут использовать памятку из предыдущего выполнения.
Обратите внимание, что это предполагает, что дорогостоящий (x) является истинной функцией и не зависит от внешнего состояния, которое может измениться. Если дорогой (x) действительно зависит от внешнего состояния, и вы можете обнаружить, когда это состояние изменится, или вы знаете, что оно не изменится во время понимания списка, вы можете reset сохранить заметки перед пониманием.
У меня будет предпочтение:
itertools.ifilter(bool, (expensive(x) for x in mylist))
Это имеет то преимущество, что:
Простой старое использование цикла for
для добавления в список тоже:
result = []
for x in mylist:
expense = expensive(x)
if expense:
result.append(expense)