Как эффективно отфильтровать вычисленные значения в понимании списка Python? - PullRequest
20 голосов
/ 25 сентября 2008

Синтаксис понимания списка 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?

Ответы [ 9 ]

22 голосов
/ 25 сентября 2008

Придумал свой собственный ответ после минуты мысли. Это можно сделать с помощью вложенного понимания:

result = [y for y in (expensive(x) for x in mylist) if y]

Полагаю, это работает, хотя я считаю, что вложенные понимания читаются лишь незначительно

21 голосов
/ 25 сентября 2008

Если вычисления уже хорошо объединены в функции, как насчет использования filter и map?

result = filter (None, map (expensive, mylist))

Вы можете использовать itertools.imap, если список очень большой.

7 голосов
/ 25 сентября 2008

Самый очевидный (и я бы сказал, самый читаемый) ответ - не использовать выражение списка или выражение генератора, а реальный генератор:

def gen_expensive(mylist):
    for item in mylist:
        result = expensive(item)
        if result:
            yield result

Требуется больше горизонтального пространства, но гораздо проще увидеть, что он делает с первого взгляда, и в итоге вы не повторяетесь.

6 голосов
/ 25 сентября 2008
result = [x for x in map(expensive,mylist) if x]

map () вернет список значений каждого объекта в mylist, переданных в CostA (). Затем вы можете понять это и отбросить ненужные значения.

Это похоже на вложенное понимание, но должно быть быстрее (поскольку интерпретатор python может довольно легко его оптимизировать).

5 голосов
/ 25 сентября 2008

Это именно то, для чего подходят генераторы:

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
  1. Это дает полное представление о том, что происходит на каждой стадии конвейера.
  2. Явный над неявным
  3. Использование генераторов везде до последнего шага, поэтому нет больших промежуточных списков

cf: «Уловки генераторов для системных программистов» Дэвида Бизли

2 голосов
/ 25 сентября 2008

Вы могли бы запомнить дорогое (x) (и если вы часто звоните дорого (x), вам, вероятно, следует запомнить это любым способом. На этой странице приведена реализация памятки для python:

http://code.activestate.com/recipes/52201/

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

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

2 голосов
/ 25 сентября 2008

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

Вот только одна из многих реализаций memoize в качестве декоратора .

1 голос
/ 17 мая 2009

У меня будет предпочтение:

itertools.ifilter(bool, (expensive(x) for x in mylist))

Это имеет преимущество:

  • избежать None как функции (будет исключено в Python 3): http://bugs.python.org/issue2186
  • использовать только итераторы.
0 голосов
/ 17 мая 2009

Существует простое старое использование цикла for для добавления в список:

result = []
for x in mylist:
    expense = expensive(x)
    if expense:
        result.append(expense)
...