Можно ли захватить возвращаемое значение из понимания списка Python для использования условия?

Я хочу создать значение в понимании списка, но также отфильтровать это значение. Например:

[expensive_function(x) for x in generator where expensive_function(x) < 5]

Я хочу избежать вызова expensive_function дважды за итерацию.

generator может возвращать бесконечную серию, а понимание списка не оценивается лениво. Так что это не сработает:

[y in [expensive_function(x) for x in generator where expensive_function(x)] where y < 5]

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


person Joe    schedule 06.01.2012    source источник
comment
Достоин ли какой-либо из ответов быть принятым? Если нет, то какую информацию вы все еще ищете?   -  person Ethan Furman    schedule 10.03.2012
comment
Извините, забыл отметить это. Спасибо за ваш ответ!   -  person Joe    schedule 11.03.2012
comment
Без проблем. Я надеялся, что тебе просто нужно напоминание. :)   -  person Ethan Furman    schedule 11.03.2012


Ответы (4)


Если generator может быть бесконечным, вы не хотите использовать понимание списка. И не все должно быть однострочным.

def filtered_gen(gen):
    for item in gen:
        result = expensive_function(item)
        if result < 5:
            yield result
person Ethan Furman    schedule 06.01.2012
comment
Должны ли последние два вхождения item быть заменены на result? - person Chris; 06.01.2012
comment
+1 к этому. Вы можете использовать itertools и выражения генератора, но это гораздо проще понять. - person ; 07.01.2012

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

Итак, у вас есть понимание списка, подобное этому:

[expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0]

И вы хотите избежать двойного вычисления expensive_function, когда он проходит ваш фильтр. Языки с более выразительным синтаксисом обработки (Scala, Haskell и т. д.) позволяют вам просто присваивать имена выражениям, вычисляемым на основе переменных обработки, что позволяет вам делать следующее:

# NOT REAL PYTHON
[result for x in xrange(5) for result = expensive_function(x) if result % 2 == 0]

Но вы можете легко эмулировать это, превратив присваивание result = expensive_function(x) в другую for итерацию над последовательностью одного элемента:

[result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0]

И доказательство:

>>> def expensive_function(x):
        print 'expensive_function({})'.format(x)
        return x + 10
>>> [expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0]
expensive_function(0)
expensive_function(0)
expensive_function(1)
expensive_function(2)
expensive_function(2)
expensive_function(3)
expensive_function(4)
expensive_function(4)
[10, 12, 14]
>>> [result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0]
expensive_function(0)
expensive_function(1)
expensive_function(2)
expensive_function(3)
expensive_function(4)
[10, 12, 14]
person Ben    schedule 27.03.2012

вы должны сделать 2 выражения генератора:

ys_all = (expensive(x) for x in xs)
ys_filtered = (y for y in ys_all if y <5)

or

from itertools import imap, ifilter
ys = ifilter(lambda y : y < 5, imap(expensive, xs))
person Simon Bergot    schedule 06.01.2012
comment
Неа. Нет, если xs бесконечно. К сожалению, в Python нет понимания списков Haskell. Что-то где-то взорвется. - person Joe; 06.01.2012
comment
Да! Это лучший ответ на мой взгляд! Не могли бы вы назвать их генераторами, а не списками? - person Victor Savu; 26.06.2016

Предупреждение Это немного запутанно, но работает. Я буду использовать пример, чтобы объяснить это.

Скажем expensive_function = math.sin

infinite generator = collections.count(0.1,0.1)

тогда

[z for z in (y if y < 5 else next(iter([])) 
     for y in (math.sin(x) for x in itertools.count(0.1,0.1)))]

is

[0.09983341664682815,
 0.19866933079506122,
 0.2955202066613396,
 0.3894183423086505,
 0.479425538604203]

Так что ваша проблема сводится к

[z for z in (y if y < 0.5 else next(iter([])) \
         for y in (expensive_function(x) for x in generator))]

Хитрость заключается в том, чтобы вызвать StopIteration из генератора и ничего более элегантного, чем next(iter([]))

Здесь expensive_function вызывается только один раз за итерацию.

Расширьте бесконечный генератор с помощью конечного генератора с условием остановки. Поскольку генератор не допускает raise StopIteration, мы выбираем запутанный способ, т.е. next(iter([])) И теперь у вас есть конечный генератор, который можно использовать в понимании списка.

Поскольку OP был связан с применением вышеуказанного метода для не-монотонной функции, здесь есть фиктивный немонотонная функция

Дорогая немонотонная функция f(x) = random.randint(1,100)*x

Условие остановки = < 7

[z for z in (y if y < 7 else next(iter([])) for y in 
      (random.randint(1,10)*x for x in itertools.count(0.1,0.1)))]

[0.9,
 0.6000000000000001,
 1.8000000000000003,
 4.0,
 0.5,
 6.0,
 4.8999999999999995,
 3.1999999999999997,
 3.5999999999999996,
 5.999999999999999]

Кстати: sin в прямом смысле немонотонна во всем диапазоне (0,2pi)

person Abhijit    schedule 06.01.2012
comment
Псих! Это немного отличается, потому что sin — немонотонная функция (я не говорил, что expensive_function монотонна, но это так!) и это останавливает в первый раз условие неверно, не продолжается для всех условий, где условие истинно. Тем не менее, если бы он был немонотонным, это привело бы к бесконечной оценке... - person Joe; 06.01.2012
comment
@Joe, это будет работать даже для немонотонной функции. Смотрите мое обновление - person Abhijit; 06.01.2012