Понимание списка без [] в Python


86

Присоединение к списку:

>>> ''.join([ str(_) for _ in xrange(10) ])
'0123456789'

join должен принимать итерацию.

Видимо, joinаргумент есть [ str(_) for _ in xrange(10) ], и это понимание списка .

Посмотри на это:

>>>''.join( str(_) for _ in xrange(10) )
'0123456789'

Теперь joinаргумент «s просто str(_) for _ in xrange(10), нет [], но результат тот же.

Почему? Имеет str(_) for _ in xrange(10)также составить список или итератор?


1
Я полагаю, что joinэто, скорее всего, написано на C и поэтому работает намного быстрее, чем понимание списка ... Время тестирования!
Джоэл Корнетт,

Видимо, я совершенно неправильно прочитал ваш вопрос. Кажется, он возвращает мне генератор ...
Джоэл Корнетт

18
Замечание: _не имеет особого значения, это обычное имя переменной. Его часто используют как одноразовое имя, но это не так (вы используете переменную). Я бы избегал использовать это в коде (по крайней мере, таким образом).
rplnt

Ответы:


69
>>>''.join( str(_) for _ in xrange(10) )

Это называется выражением генератора и объясняется в PEP 289 .

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

Обратите внимание, что есть третий способ написать выражение:

''.join(map(str, xrange(10)))

1
Насколько я знаю, генератор можно создать с помощью кортежного выражения, например ( str(_) for _ in xrange(10) ). Но я был сбит с толку, почему ()можно опустить join, что означает, что код должен быть похож на `` '' .join ((str (_) for _ in xrange (10))), верно?
Alcott

2
@Alcott Насколько я понимаю, кортежи на самом деле определяются списком выражений, разделенных запятыми, а не скобками; круглые скобки служат только для визуальной группировки значений в назначении или для фактической группировки значений, если кортеж попадает в какой-либо другой список, разделенный запятыми, например, при вызове функции. Это часто демонстрируется запуском кода вроде tup = 1, 2, 3; print(tup). Имея это в виду, использование forв качестве части выражения создает генератор, а скобки служат только для того, чтобы отличить его от неправильно написанного цикла.
Эрик Эд

133

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

В целом, genexps (так их ласково называют) более эффективны с точки зрения памяти и быстрее, чем составление списков.

ОДНАКО, в этом случае ''.join()понимание списка происходит быстрее и эффективнее с точки зрения памяти. Причина в том, что объединению необходимо выполнить два прохода по данным, поэтому ему действительно нужен реальный список. Если вы дадите ему один, он сразу же начнет свою работу. Если вместо этого вы дадите ему genexp, он не сможет начать работу до тех пор, пока не создаст новый список в памяти, запустив genexp до исчерпания:

~ $ python -m timeit '"".join(str(n) for n in xrange(1000))'
1000 loops, best of 3: 335 usec per loop
~ $ python -m timeit '"".join([str(n) for n in xrange(1000)])'
1000 loops, best of 3: 288 usec per loop

Тот же результат сохраняется при сравнении itertools.imap с картой :

~ $ python -m timeit -s'from itertools import imap' '"".join(imap(str, xrange(1000)))'
1000 loops, best of 3: 220 usec per loop
~ $ python -m timeit '"".join(map(str, xrange(1000)))'
1000 loops, best of 3: 212 usec per loop

4
@lazyr Твой второй тайминг слишком много работает. Не оборачивайте genexp вокруг listcomp - просто используйте genexp напрямую. Неудивительно, что у вас странные сроки.
Raymond Hettinger

11
Не могли бы вы объяснить, почему ''.join()для построения строки требуется 2 прохода через итератор?
ovgolovin

28
@ovgolovin Я предполагаю, что первый проход - это суммирование длин строк, чтобы иметь возможность выделить правильный объем памяти для объединенной строки, а второй проход - для копирования отдельных строк в выделенное пространство.
Лауриц В. Таулов

20
@lazyr Это предположение верное. Именно это и делает str.join :-)
Раймонд Хеттингер

4
Иногда мне очень не хватает возможности «добавить в избранное» конкретный ответ на SO.
Воздух,

5

Во втором примере используется выражение генератора, а не понимание списка. Разница в том, что при понимании списка список полностью создается и передается в .join(). С помощью выражения генератора элементы генерируются один за другим и потребляются .join(). Последний использует меньше памяти и обычно работает быстрее.

Как это бывает, конструктор списка с радостью использует любую итерацию, включая выражение генератора. Так:

[str(n) for n in xrange(10)]

просто "синтаксический сахар" для:

list(str(n) for n in xrange(10))

Другими словами, понимание списка - это просто выражение генератора, которое превращается в список.


2
Вы уверены, что они эквивалентны под капотом? Timeit говорит:: [str(x) for x in xrange(1000)]262 мкс,: list(str(x) for x in xrange(1000))304 мкс.
Лауриц В. Таулов

2
@lazyr Вы правы. Понимание списка происходит быстрее. И это причина того, что понимание списков просачивается в Python 2.x. Вот что пишет GVR: "" Это было артефактом исходной реализации понимания списков; это был один из «маленьких грязных секретов» Python в течение многих лет. Это началось как преднамеренный компромисс, чтобы сделать понимание списков ослепляюще быстрым, и хотя это не было распространенной ловушкой для новичков, это определенно иногда ужалило
ovgolovin

3
@ovgolovin Причина, по которой listcomp работает быстрее, заключается в том, что join должен создать список, прежде чем он сможет начать работу. "Утечка", о которой вы говорите, не является проблемой скорости - это просто означает, что переменная индукции цикла отображается за пределами listcomp.
Raymond Hettinger

1
@RaymondHettinger Тогда что означают эти слова: «Все началось как намеренный компромисс, чтобы сделать понимание списка ослепительно быстрым »? Как я понял, есть связь их утечки с проблемами скорости. GVR также написал: «Для выражений генератора мы не могли этого сделать. Выражения генератора реализованы с помощью генераторов, для выполнения которых требуется отдельный фрейм исполнения. Таким образом, выражения генератора (особенно если они повторяются по короткой последовательности) были менее эффективны, чем понимание списков . "
ovgolovin

4
@ovgolovin Вы совершили неверный переход от детали реализации listcomp к тому, почему str.join работает именно так. Одна из первых строк кода str.join - seq = PySequence_Fast(orig, "");это единственная причина, по которой итераторы работают медленнее, чем списки или кортежи при вызове str.join (). Вы можете начать чат, если хотите обсудить это дальше (я автор PEP 289, создатель кода операции LIST_APPEND и тот, кто оптимизировал конструктор list (), так что у меня есть знакомство с вопросом).
Raymond Hettinger


4

Если оно заключено в круглые, но не квадратные скобки, технически это выражение генератора. Генераторные выражения были впервые представлены в Python 2.4.

http://wiki.python.org/moin/Generators

Часть после соединения ( str(_) for _ in xrange(10) )сама по себе является выражением генератора. Вы можете сделать что-то вроде:

mylist = (str(_) for _ in xrange(10))
''.join(mylist)

и это означает то же самое, что вы написали во втором случае выше.

Генераторы обладают некоторыми очень интересными свойствами, не последним из которых является то, что они не выделяют весь список, когда он вам не нужен. Вместо этого такая функция, как join, «выкачивает» элементы из выражения генератора по одному, выполняя свою работу с крошечными промежуточными частями.

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


1

Это генератор, а не понимание списка. Генераторы также являются итерабельными, но вместо того, чтобы сначала создать весь список, а затем передать его для соединения, он передает каждое значение в xrange одно за другим, что может быть намного более эффективным.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.