Ответ 1
Вы должны использовать генератор:
[s for s in (square(x) for x in range(12)) if s > 50]
Это позволяет избежать создания промежуточного нефильтрованного списка квадратов.
Рассмотрим следующий пример игрушки:
>>> def square(x): return x*x
...
>>> [square(x) for x in range(12) if square(x) > 50]
[64, 81, 100, 121]
Мне нужно дважды называть квадрат (x) в понимании списка. Дублирование является уродливым, подверженным ошибкам (легко изменить только один из двух вызовов при изменении кода) и неэффективно.
Конечно, я могу это сделать:
>>> squares = [square(x) for x in range(12)]
>>> [s for s in squares if s > 50]
[64, 81, 100, 121]
или это:
[s for s in [square(x) for x in range(12)] if s > 50]
Они оба пригодны для жизни, но кажется, что может быть способ сделать все это в одном заявлении, не вставляя два понимания списка, и я знаю, что мне придется долго смотреть на него в следующий раз, Чтение кода просто для выяснения того, что происходит. Есть ли способ?
Я думаю, что справедливый вопрос, чтобы спросить меня, будет таким, как я себе представляю, такой синтаксис мог бы выглядеть. Вот две идеи, но они не кажутся идиоматическими в Python (и они не работают). Они вдохновлены анафорическими макросами в Lisp.
[square(x) for x in range(12) if it > 50]
[it=square(x) for x in range(12) if it > 50]
Вы должны использовать генератор:
[s for s in (square(x) for x in range(12)) if s > 50]
Это позволяет избежать создания промежуточного нефильтрованного списка квадратов.
Другая альтернатива, использующая "скованные" списки, а не вложенные:
[s for n in range(12) for s in [square(n)] if s > 50]
Возможно, это странное чтение.
Ниже приведено сравнение вложенных генераторов vs "закованных в цепочку" списков и вычислений дважды
$ python -m timeit "[s for n in range(12) for s in [n * n] if s > 50]"
100000 loops, best of 3: 2.48 usec per loop
$ python -m timeit "[s for s in (x * x for x in range(12)) if s > 50]"
1000000 loops, best of 3: 1.89 usec per loop
$ python -m timeit "[n * n for n in range(12) if n * n > 50]"
1000000 loops, best of 3: 1.1 usec per loop
$ pypy -m timeit "[s for n in range(12) for s in [n * n] if s > 50]"
1000000 loops, best of 3: 0.211 usec per loop
$ pypy -m timeit "[s for s in (x * x for x in range(12)) if s > 50]"
1000000 loops, best of 3: 0.359 usec per loop
$ pypy -m timeit "[n * n for n in range(12) if n * n > 50]"
10000000 loops, best of 3: 0.0834 usec per loop
Я использовал n * n
вместо square(n)
, потому что это было удобно и удаляет служебные данные вызова функции из benckmark
TL;DR: для простых случаев лучше всего просто продублировать вычисления.
[square(s) for s in range(12) if s >= 7] # sqrt(50) = 7.071...
Или даже проще (без ветвления, woo!)
[square(s) for s in range(7, 12)] # sqrt(50) = 7.071...
EDIT: я слепой, дублированный ответ Eevee.
Можно использовать итерацию по списку из 1 элемента для "связывания" промежуточных переменных:
[s for x in range(12) for s in [square(x)] if s > 50]
Я не решаюсь рекомендовать это как читаемое решение.
Pro: По сравнению с вложенным пониманием, я предпочитаю порядок здесь - наличие for x in range(12)
снаружи. Вы можете просто читать его последовательно, вместо того, чтобы увеличивать масштаб, а затем отступать...
Con: for s in [...]
является неидиоматическим взломом и может дать читателям паузу. Вложенное понимание, возможно, более трудное для расшифровки, по крайней мере, использует языковые функции "очевидным образом".
tmp
, я мог бы сделать ее более ясной.Суть в том, что я тоже не доволен. Вероятно, наиболее читаемым является обозначение промежуточного генератора:
squares = (square(x) for x in range(12))
result = [s for s in squares if s > 50]
[Боковое примечание: наименования наименований генераторов немного редки. Но прочитайте лекцию Дэвида Бэйсли, и она может расти на вас.]
OTOH, если вы собираетесь писать такие конструкции много, пойдите для шаблона for tmp in [expr(x)]
- он станет "локально идиоматическим" в вашем коде и когда-то знакомым, его компактность окупится. Моя озабоченность читабельностью больше связана с одноразовым использованием...