Использование нотации понимания списков в Python

Бизнес

В Python очень просто использовать нотацию понимания списка при создании нового списка.(List comprehensions)

В этой статье мы сначала обсудим следующее

  • Основной тип обозначения понимания списка
  • Нотация понимания списка с условным ветвлением по if
  • Комбинация с тернарными операторами (обработка типа if else)
  • zip(),enumerate()Сочетание с этими
  • обозначение включения вложенного списка

Далее мы объясним набор обозначений для понимания списков с примерами кода.

  • обозначение включения множества(Set comprehensions)
  • обозначение включения в словарь(Dict comprehensions)
  • тип генератора(Generator expressions)

Основной тип обозначения понимания списка

Нотация понимания списка записывается следующим образом.

[Expression for Any Variable Name in Iterable Object]

Он принимает каждый элемент итерируемого объекта, такого как список, кортеж или диапазон, по произвольному имени переменной и оценивает его с помощью выражения. Возвращается новый список с результатом оценки в качестве элемента.

Пример приводится вместе с эквивалентным выражением for.

squares = [i**2 for i in range(5)]
print(squares)
# [0, 1, 4, 9, 16]
squares = []
for i in range(5):
    squares.append(i**2)

print(squares)
# [0, 1, 4, 9, 16]

Тот же процесс можно проделать с помощью map(), но для простоты и ясности предпочтительнее использовать нотацию понимания списка.

Нотация понимания списка с условным ветвлением по if

Условное ветвление с помощью if также возможно. Запишите if в постфиксе следующим образом.

[Expression for Any Variable Name in Iterable Object if Conditional Expression]

Выражением оцениваются только те элементы объекта iterable, условное выражение которых истинно, и возвращается новый список, элементы которого являются результатом.

В условном выражении можно использовать любое имя переменной.

Пример приводится вместе с эквивалентным выражением for.

odds = [i for i in range(10) if i % 2 == 1]
print(odds)
# [1, 3, 5, 7, 9]
odds = []
for i in range(10):
    if i % 2 == 1:
        odds.append(i)

print(odds)
# [1, 3, 5, 7, 9]

Тот же процесс можно проделать с помощью filter(), но для простоты и ясности предпочтительнее использовать нотацию понимания списка.

Комбинация с тернарными операторами (обработка типа if else)

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

Если вы хотите переключить процесс в зависимости от условия или по-разному обработать элементы, не удовлетворяющие условию, как в if else, используйте троичный оператор.

В языке Python троичный оператор можно записать следующим образом

Value When True if Conditional Expression else Value When False

Это используется в части выражения в обозначении понимания списка, как показано ниже.

[Value When True if Conditional Expression else Value When False for Any Variable Name in Iterable Object]

Пример приводится вместе с эквивалентным выражением for.

odd_even = ['odd' if i % 2 == 1 else 'even' for i in range(10)]
print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
odd_even = []
for i in range(10):
    if i % 2 == 1:
        odd_even.append('odd')
    else:
        odd_even.append('even')

print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']

Также можно писать выражения, используя произвольные имена переменных для значений true и false.

Если условие выполнено, выполняется некоторая обработка, в противном случае значение исходного объекта iterable остается неизменным.

odd10 = [i * 10 if i % 2 == 1 else i for i in range(10)]
print(odd10)
# [0, 10, 2, 30, 4, 50, 6, 70, 8, 90]

Комбинация с zip() и enumerate()

Полезные функции, которые часто используются в операторе for, включают zip(), которая объединяет несколько итераций, и enumerate(), которая возвращает значение вместе с его индексом.

Конечно, можно использовать zip() и enumerate() с нотацией понимания списка. Это не специальный синтаксис, и это не сложно, если учитывать соответствие с оператором for.

Пример использования zip().

l_str1 = ['a', 'b', 'c']
l_str2 = ['x', 'y', 'z']

l_zip = [(s1, s2) for s1, s2 in zip(l_str1, l_str2)]
print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]
l_zip = []
for s1, s2 in zip(l_str1, l_str2):
    l_zip.append((s1, s2))

print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]

Пример enumerate().

l_enu = [(i, s) for i, s in enumerate(l_str1)]
print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]
l_enu = []
for i, s in enumerate(l_str1):
    l_enu.append((i, s))

print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]

Идея такая же, как и раньше при использовании if.

l_zip_if = [(s1, s2) for s1, s2 in zip(l_str1, l_str2) if s1 != 'b']
print(l_zip_if)
# [('a', 'x'), ('c', 'z')]

Каждый элемент также может быть использован для вычисления нового элемента.

l_int1 = [1, 2, 3]
l_int2 = [10, 20, 30]

l_sub = [i2 - i1 for i1, i2 in zip(l_int1, l_int2)]
print(l_sub)
# [9, 18, 27]

обозначение включения вложенного списка

Как и вложенные циклы for, нотация понимания списка также может быть вложенной.

[Expression for Variable Name 1 in Iterable Object 1
    for Variable Name 2 in Iterable Object 2
        for Variable Name 3 in Iterable Object 3 ... ]

Для удобства добавлены переносы строк и отступы, но они не обязательны для грамматики; их можно продолжить на одной строке.

Пример приводится вместе с эквивалентным выражением for.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

flat = [x for row in matrix for x in row]
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
flat = []
for row in matrix:
    for x in row:
        flat.append(x)

print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Также можно использовать несколько переменных.

cells = [(row, col) for row in range(3) for col in range(2)]
print(cells)
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

Вы также можете сделать условное ветвление.

cells = [(row, col) for row in range(3)
         for col in range(2) if col == row]
print(cells)
# [(0, 0), (1, 1)]

Также возможно условное ветвление для каждого итерируемого объекта.

cells = [(row, col) for row in range(3) if row % 2 == 0
         for col in range(2) if col % 2 == 0]
print(cells)
# [(0, 0), (2, 0)]

обозначение включения множества(Set comprehensions)

Замена квадратных скобок [] в нотации list comprehension на фигурные скобки {} создает множество (объект типа set).

{Expression for Any Variable Name in Iterable Object}
s = {i**2 for i in range(5)}

print(s)
# {0, 1, 4, 9, 16}

обозначение включения в словарь(Dict comprehensions)

Словари (объекты типа dict) также могут быть созданы с помощью нотации comprehension.

{}, и укажите ключ и значение в части выражения как ключ: значение.

{Key: Value for Any Variable Name in Iterable Object}

Для ключа и значения может быть указано любое выражение.

l = ['Alice', 'Bob', 'Charlie']

d = {s: len(s) for s in l}
print(d)
# {'Alice': 5, 'Bob': 3, 'Charlie': 7}

Чтобы создать новый словарь из списка ключей и значений, используйте функцию zip().

keys = ['k1', 'k2', 'k3']
values = [1, 2, 3]

d = {k: v for k, v in zip(keys, values)}
print(d)
# {'k1': 1, 'k2': 2, 'k3': 3}

тип генератора(Generator expressions)

Если квадратные скобки [] в нотации list comprehensions используются как круглые скобки (), то вместо кортежа возвращается генератор. Это называется генераторными выражениями.

Пример обозначения понимания списка.

l = [i**2 for i in range(5)]

print(l)
# [0, 1, 4, 9, 16]

print(type(l))
# <class 'list'>

Пример выражения генератора. Если вы распечатаете() генератор как он есть, он не выведет свое содержимое, но если вы запустите его с помощью оператора for, вы сможете получить содержимое.

g = (i**2 for i in range(5))

print(g)
# <generator object <genexpr> at 0x10af944f8>

print(type(g))
# <class 'generator'>

for i in g:
    print(i)
# 0
# 1
# 4
# 9
# 16

Выражения-генераторы также допускают условное ветвление и вложенность с использованием if, а также нотации понимания списка.

g_cells = ((row, col) for row in range(0, 3)
           for col in range(0, 2) if col == row)

print(type(g_cells))
# <class 'generator'>

for i in g_cells:
    print(i)
# (0, 0)
# (1, 1)

Например, если список с большим количеством элементов генерируется с использованием нотации понимания списка, а затем выполняется цикл с помощью оператора for, то при использовании нотации понимания списка список, содержащий все элементы, будет сгенерирован в начале. С другой стороны, если использовать выражение-генератор, то при каждом повторении цикла элементы будут генерироваться по одному, что уменьшит объем используемой памяти.

Если выражение генератора является единственным аргументом функции, круглые скобки () можно опустить.

print(sum([i**2 for i in range(5)]))
# 30

print(sum((i**2 for i in range(5))))
# 30

print(sum(i**2 for i in range(5)))
# 30

Что касается скорости обработки, то нотация понимания списка часто быстрее, чем нотация генератора, когда обрабатываются все элементы.

Однако, например, при суждении с помощью all() или any() результат определяется при наличии false или true, поэтому использование выражений-генераторов может быть быстрее, чем использование нотации понимания списка.

Нотации понимания кортежа не существует, но если вы используете выражение-генератор в качестве аргумента tuple(), вы можете сгенерировать кортеж в нотации понимания.

t = tuple(i**2 for i in range(5))

print(t)
# (0, 1, 4, 9, 16)

print(type(t))
# <class 'tuple'>
Copied title and URL