Понимание генераторов списков обеспечивает краткий способ создания списков.
В версии Python 3.12 ускорено выполнение генераторов словарей, списков и множеств до двух раз. Смотрите подраздел "Улучшения в Python 3.12 для генераторов..."
Обычно внутри приложений создаются новые списки, в которых каждый элемент является результатом некоторых операций, примененных к каждому члену другой последовательности или итерации, или создаются подпоследовательности тех элементов, которые удовлетворяют определенному условию.
Например мы хотим создать список квадратов:
>>> squares = [] >>> for x in range(10): ... squares.append(x**2) ... >>> squares # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Обратите внимание, что код выше создает или перезаписывает переменную с именем x
, которая все еще существует после завершения цикла. Мы можем вычислить список квадратов без каких-либо побочных эффектов, используя:
squares = list(map(lambda x: x**2, range(10))) # это эквивалентно: squares = [x**2 for x in range(10)]
Последняя запись является более краткой и читабельной. "Список - выражение" состоит из скобок, внутри которых, сначала идет нужное нам выражение, за которым следует предложение for ... in
, далее выражение может включать ноль или более подвыражений for ... in
или предложения if ... else
.
Результатом будет новый список, полученный в результате оценки выражения в контексте предложений for ... in
и if ... else
которые следуют за ним. Например, этот listcomp
объединяет элементы двух списков, если они не равны:
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y] [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)] # это эквивалентно: >>> combs = [] >>> for x in [1,2,3]: ... for y in [3,1,4]: ... if x != y: ... combs.append((x, y)) ... >>> combs [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
Обратите внимание, что порядок операторов for ... in
and if ... else
одинаков в обоих этих фрагментах.
Если нужное нам выражение является кортежем, как в предыдущем примере, оно должно быть заключено в скобки (x, y)
.
Начальным выражением генератора списка может быть любое произвольное выражение, включая другой генератор списка.
Рассмотрим следующий пример матрицы 3x4, реализованной в виде списка из трех списков длиной 4:
>>> matrix = [ ... [1, 2, 3, 4], ... [5, 6, 7, 8], ... [9, 10, 11, 12], ... ]
Следующий генератор списка будет транспонировать строки и столбцы:
>>> [[row[i] for row in matrix] for i in range(4)] # [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
Вложенный генератор списка оценивается в контексте следующего за ним for ... in
, поэтому этот пример эквивалентен:
>>> transposed = [] >>> for i in range(4): ... transposed.append([row[i] for row in matrix]) ... >>> transposed # [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
Код выше, в свою очередь, совпадает с таким кодом:
>>> transposed = [] >>> for i in range(4): ... # the following 3 lines implement the nested listcomp ... transposed_row = [] ... for row in matrix: ... transposed_row.append(row[i]) ... transposed.append(transposed_row) ... >>> transposed # [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
В реальном коде нужно отдавать предпочтение встроенным функциям, а сложные выражения использовать, если нет альтернативы. Функция zip() отлично подойдет для случая, который разбирался выше:
>>> list(zip(*matrix)) # [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
Ну и напоследок распишем вложенный список-выражение для большей наглядности. Так как Python разрешает переносить все, что находится в скобках, вложенный список-выражение можно записать так:
>>> matrix = [ ... [1, 2, 3, 4], ... [5, 6, 7, 8], ... [9, 10, 11, 12], ... ] >>> [ ... [ ... row[i] ... for row in matrix ... ] ... for i in range(4) ... ] # [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
Да-да... И все будет работать!
Дополнительно смотрите материал "Эффективные приемы использования генераторов списков".
В версии Python 3.12 генераторы словарей, списков и множеств теперь встроены в Python, а не создают новый одноразовый функциональный объект для каждого выполнения вызова. Это ускоряет выполнение таких генераторов до двух раз.
Переменные итерации выражений-генераторов остаются изолированными и не перезаписывают переменную с тем же именем во внешней области, а также не становятся видимыми после выражения. Встраивание выражений-генераторов в Python действительно приводит к нескольким видимым изменениям в поведении:
symtable
больше не будет создавать дочерние таблицы символов для каждого выражения-генератора. Теперь локальные значения выражения-генератора будут включены в таблицу символов родительской функции.locals()
внутри выражения-генератора теперь включает переменные извне и больше не включает синтетическую переменную .0
для "аргумента" выражения-генератора.Выражение-генератора, выполняющее итерацию непосредственно через locals()
(например, [k for k in locals()]
) при запуске в режиме трассировки (например, измерение покрытия кода), может вызвать исключение "RuntimeError: dictionary changed size during iteration". Это то же самое поведение, которое уже наблюдалось в циклах for k in locals():
. Чтобы избежать ошибки, сначала необходимо создать список ключей для перебора:
>>> keys = list(locals()) >>> [k for k in keys]
В версии Python 3.12, переменные, используемые в целевой части выражений-генераторов, которые не сохраняются, теперь могут использоваться в выражениях присваивания (:=). Например, в [(b := 1) for a, b.prop in some_iter]
теперь разрешено присвоение b
. Обратите внимание, что присвоение переменных, хранящихся в целевой части понятий (например, a
), по-прежнему запрещено. (Добавлено Никитой Соболевом.)
>>> vec = [-4, -2, 0, 2, 4] # новый список с удвоенными значениями >>> [x*2 for x in vec] # [-8, -4, 0, 4, 8] # фильтр списка для исключения отрицательных чисел >>> [x for x in vec if x >= 0] # [0, 2, 4] # применить функцию ко всем элементам >>> [abs(x) for x in vec] # [4, 2, 0, 2, 4] # вызов метода для каждого элемента >>> freshfruit = [' banana', ' loganberry ', 'passion fruit '] >>> [weapon.strip() for weapon in freshfruit] # ['banana', 'loganberry', 'passion fruit'] # создаст список из кортежей типа (число, квадрат) >>> [(x, x**2) for x in range(6)] # [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)] # кортеж должен быть заключен в скобки, # иначе возникнет ошибка >>> [x, x**2 for x in range(6)] # File "<stdin>", line 1, in <module> # [x, x**2 for x in range(6)] # ^ # SyntaxError: invalid syntax # сгладим список с помощью двух выражений 'for ... in' >>> vec = [[1,2,3], [4,5,6], [7,8,9]] >>> [num for elem in vec for num in elem] # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Так как Python разрешает переносить все, что находится в скобках, то для более глубокого понимания последнее выражение в примере выше можно записать так:
>>> vec = [ ... [1,2,3], ... [4,5,6], ... [7,8,9] ] >>> [ ... num ... for elem in vec ... for num in elem ... ] # [1, 2, 3, 4, 5, 6, 7, 8, 9] # или вот еще пример 'понятной' записи >>> import random >>> n = 10 >>> tree = [ ... ' '*(n-i)+'/'+''.join(random.choice(' # *') ... for _ in range(2*i))+'\\' ... for i in range(n) ... ] >>> print('\n'.join(tree)) # /\ # / *\ # /# *\ # / * ##\ # / * #*\ # /# ** * * \ # /# #*# * *#\ # / **## * #\ # / * ** * #*# #\ # /** **#*## ** # #*\
И самое главное все работает, правда здорово! Используйте эту приятную особенность языка Python в своем коде, что-бы он был более понятным другим.
Генераторы списков могут содержать сложные подвыражения и вложенные функции:
>>> from math import pi >>> [str(round(pi, i)) for i in range(1, 6)] # ['3.1', '3.14', '3.142', '3.1416', '3.14159']