Можно ли добавить предложение where со списком? - программирование
Подтвердить что ты не робот

Можно ли добавить предложение where со списком?

Рассмотрим следующее понимание списка

[ (x,f(x)) for x in iterable if f(x) ]

Это фильтрует итерируемое основанное на условии f и возвращает пары x,f(x). Проблема с этим подходом состоит в том, что f(x) вычисляется дважды. Было бы здорово, если бы мы могли писать как

[ (x,fx) for x in iterable if fx where fx = f(x) ]
or
[ (x,fx) for x in iterable if fx with f(x) as fx ]

Но в Python мы должны писать с использованием вложенных пониманий, чтобы избежать повторного вызова f (x), и это делает понимание менее ясным

[ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ]

Есть ли другой способ сделать его более питонным и читабельным?


Обновление Скоро в Python 3.8! PEP

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]
4b9b3361

Ответ 1

Вы пытаетесь использовать семантику let -statement в пониманиях списков python, область видимости которой доступна как для ___ for..in (map), так и для if ___ (filter) части понимания, и область действия которой зависит от ..for ___ in....


Ваше решение, измененное: Ваш (как вы допускаете нечитаемое) решение [ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ] - самый простой способ написать оптимизацию.

Основная идея: поднять x в кортеж (x, f (x)).

Некоторые утверждают, что самый "питонический" способ сделать что-то будет оригинальным [(x,f(x)) for x in iterable if f(x)] и принять неэффективность.

Однако вы можете разделить ((y,fy) for y in iterable) на функцию, если вы планируете сделать это много. Это плохо, потому что, если вы когда-либо захотите получить доступ к большему количеству переменных, чем x,fx (например, x,fx,ffx), вам нужно будет переписать все ваши списки. Поэтому это не отличное решение, если вы точно не знаете, что вам нужно только x,fx и планируете повторно использовать этот шаблон.


Выражение генератора:

Основная идея: используйте более сложную альтернативу выражениям генератора: один, где python позволит вам писать несколько строк.

Вы могли бы просто использовать выражение генератора, с которым python хорошо играет:

def xfx(iterable):
    for x in iterable:
        fx = f(x)
        if fx:
            yield (x,fx)

xfx(exampleIterable)

Вот как я лично это сделал.


запоминанием/кэширование:

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

У этого может быть немного накладных расходов, и требуется политика в отношении того, насколько большой кеш должен быть, и когда он должен быть собран из мусора. Таким образом, это следует использовать только в том случае, если у вас будет другое использование для memoizing f, или если f очень дорого. Но это позволит вам писать...

[ (x,f(x)) for x in iterable if f(x) ]

... как вы изначально хотели, без повышения производительности, выполняя дорогостоящие операции в f дважды, даже если вы технически называете это дважды. Вы можете добавить декоратор @memoized в f: пример (без максимального размера кеша). Это будет работать до тех пор, пока х хешируется (например, число, кортеж, frozenset и т.д.).


Значения "пустышки":

Основная идея: захватить fx = f (x) в замыкании и изменить поведение понимания списка.

filterTrue(
    (lambda fx=f(x): (x,fx) if fx else None)() for x in iterable
)

где filterTrue (iterable) является фильтром (None, iterable). Вы должны были бы изменить это, если бы ваш тип списка (2-кортеж) действительно был None.

Ответ 2

Нет инструкции where, но вы можете "эмулировать" ее с помощью for:

a=[0]
def f(x):
    a[0] += 1
    return 2*x

print [ (x, y) for x in range(5) for y in [f(x)] if y != 2 ]
print "The function was executed %s times" % a[0]

Исполнение:

$ python 2.py 
[(0, 0), (2, 4), (3, 6), (4, 8)]
The function was executed 5 times

Как вы можете видеть, функции выполняются 5 раз, а не 10 или 9.

Эта конструкция for:

for y in [f(x)]

подделать предложение where.

Ответ 3

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

Вместо этого вы можете использовать выражение генератора.

def fun(iterable):
    for x in iterable:
        y = f(x)
        if y:
            yield x, y


print list(fun(iterable))

Ответ 4

Карта и почтовый индекс?

fnRes = map(f, iterable)
[(x,fx) for x,fx in zip(iterable, fnRes) if fx)]