Розуміння списку без [] на Python


85

Приєднання до списку:

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

join повинен взяти ітерацію.

Очевидно, joinаргумент Російської Федерації є [ str(_) for _ in xrange(10) ], і це розуміння списку .

Подивись на це:

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

Зараз joinаргумент '' просто str(_) for _ in xrange(10), ні [], але результат однаковий.

Чому? Має str(_) for _ in xrange(10)також скласти список або итератор?


1
Я гадаю, що joinце, швидше за все, написано на мові C і тому працює набагато швидше, ніж розуміння списку ... Час тестування!
Джоел Корнетт

Мабуть, я прочитав ваше запитання абсолютно неправильно. Здається, він повертає для мене генератор ...
Джоель Корнетт,

18
Просто примітка: _не має особливого значення, це звичайна назва змінної. Це часто використовується як ім’я, яке викидають, але це не так (ви використовуєте змінну). Я б уникав використовувати його в коді (принаймні таким чином).
rplnt

Відповіді:


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

Це називається генераторним виразом і пояснюється в PEP 289 .

Основна різниця між виразами генератора та розумінням списку полягає в тому, що перші не створюють список у пам'яті.

Зверніть увагу, що існує третій спосіб написання виразу:

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

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

1
@Alcott Я розумію кортежі в тому, що вони насправді визначаються списком виразів, розділених комами, а не дужками; дужки існують лише для візуального групування значень у призначенні або для фактичного групування значень, якщо кортеж потрапляв до якогось іншого списку, розділеного комами, наприклад, виклику функції. Це часто демонструється запуском коду типу tup = 1, 2, 3; print(tup). Маючи це на увазі, використання forяк частини виразу створює генератор, а дужки просто там, щоб відрізнити його від неправильно записаного циклу.
Ерік Ед Ломар

132

Інші респонденти правильно відповіли, що ви виявили генераторний вираз (який має позначення, схожі на розуміння списку, але без оточуючих квадратних дужок).

Загалом, genexps (як їх ласкаво знають) ефективніші та швидші за пам'ять, ніж розуміння списків.

Втім, як би там не було ''.join(), розуміння списку є швидшим та ефективнішим. Причина полягає в тому, що 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 та map :

~ $ 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 безпосередньо. Не дивно, що у вас є непарні терміни.
Реймонд Хеттінгер,

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

27
@ovgolovin Я вважаю, що перший прохід полягає в підсумовуванні довжин рядків, щоб мати змогу розподілити правильний обсяг пам'яті для об'єднаного рядка, тоді як другий прохід полягає у копіюванні окремих рядків у виділений простір.
Lauritz V. Thaulow

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 usec,: list(str(x) for x in xrange(1000))304 usec.
Lauritz V. Thaulow

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

3
@ovgolovin Причиною того, що listcomp є швидшим, є те, що join повинен створити список, перш ніж він зможе почати роботу. "Витік", на який ви посилаєтесь, не є проблемою швидкості - це просто означає, що змінна індукції циклу виставляється за межі listcomp.
Реймонд Хеттінгер,

1
@RaymondHettinger Тоді, що означають ці слова: "Це почалося як навмисний компроміс, щоб зробити розуміння списку сліпуче швидким "? Як я зрозумів, їх витік пов’язаний із проблемами швидкості. GVR також писав: "Для генераторських виразів ми не могли цього зробити. Вирази генератора реалізовані з використанням генераторів, для виконання яких потрібен окремий фрейм виконання. Таким чином, вирази генератора (особливо якщо вони повторюються в короткій послідовності) були менш ефективними, ніж розуміння списку . "
ovgolovin

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


4

Якщо це в паренах, але не в дужках, це технічно вираз генератора. Вирази генератора вперше були введені в Python 2.4.

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

Частина після об’єднання ( str(_) for _ in xrange(10) )сама по собі є виразом генератора. Ви можете зробити щось на зразок:

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

і це означає точно те саме, що ви писали у другому випадку вище.

Генератори мають кілька дуже цікавих властивостей, і не в останню чергу це те, що вони в кінцевому підсумку не виділяють цілий список, коли він вам не потрібен. Натомість така функція, як об’єднання, «викачує» елементи з виразу генератора по черзі, виконуючи свою роботу над крихітними проміжними частинами.

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


1

Це генератор, а не розуміння списку. Генератори також є ітерабельними, але замість того, щоб спочатку створювати весь список, а потім передавати його для приєднання, він передає кожне значення в xrange по одному, що може бути набагато ефективніше.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.