Python Language
Перечисление списков
Поиск…
Вступление
Перечисление списков в Python - это сжатые, синтаксические конструкции. Они могут использоваться для создания списков из других списков путем применения функций к каждому элементу в списке. В следующем разделе объясняется и демонстрируется использование этих выражений.
Синтаксис
- [x + 1 для x в (1, 2, 3)] # перечисление, дает [2, 3, 4]
- (x + 1 для x в (1, 2, 3)) # генераторное выражение, даст 2, затем 3, затем 4
- [x для x в (1, 2, 3), если x% 2 == 0] # понимание списка с фильтром, дает [2]
- [x + 1, если x% 2 == 0 else x для x в (1, 2, 3)] # список с тройным
- [x + 1, если x% 2 == 0 else x для x в диапазоне (-3,4), если x> 0] # список с тройной и фильтрацией
- {x for x in (1, 2, 2, 3)} # set assrehension, дает {1, 2, 3}
- {k: v для k, v в [('a', 1), ('b', 2)]} # dict понимает, дает {'a': 1, 'b': 2} (python 2.7+ и Только 3.0+)
- [x + y для x в [1, 2] для y в [10, 20]] # Вложенные петли дают [11, 21, 12, 22]
- [x + y для x в [1, 2, 3], если x> 2 для y в [3, 4, 5]] # Условие проверено на 1-ом для цикла
- [x + y для x в [1, 2, 3] для y в [3, 4, 5], если x> 2] # Условие проверено на 2-ом для цикла
- [x для x в xrange (10), если x% 2 == 0] # Условие проверено, если зацикленные числа являются нечетными числами
замечания
Понимание - синтаксические конструкции, которые определяют структуры данных или выражения, уникальные для конкретного языка. Правильное использование понятий переосмысливает их в легко понятых выражениях. В качестве выражений они могут использоваться:
- в правой части заданий
- как аргументы для вызова функций
- в теле лямбда-функции
- как самостоятельные заявления. (Например:
[print(x) for x in range(10)]
)
Список рекомендаций
Понимание списка создает новый list
, применяя выражение к каждому элементу итерабельного . Самая основная форма:
[ <expression> for <element> in <iterable> ]
Также есть необязательное условие «если»:
[ <expression> for <element> in <iterable> if <condition> ]
Каждый <element>
в <iterable>
подключается к <expression>
если (необязательно) <condition>
значение true . Все результаты возвращаются сразу в новом списке. Выражения генератора оцениваются лениво, но списки понимают всю целостность итератора, потребляющую память пропорционально длине итератора.
Чтобы создать list
целых чисел в квадрате:
squares = [x * x for x in (1, 2, 3, 4)]
# squares: [1, 4, 9, 16]
Выражение for
для каждого значения, в свою очередь, устанавливает x
(1, 2, 3, 4)
. Результат выражения x * x
добавляется во внутренний list
. Внутренний list
присваивается squares
переменных по завершении.
Помимо увеличения скорости (как поясняется здесь ), понимание списка примерно эквивалентно следующему for-loop:
squares = []
for x in (1, 2, 3, 4):
squares.append(x * x)
# squares: [1, 4, 9, 16]
Выражение, применяемое к каждому элементу, может быть как можно более сложным:
# Get a list of uppercase characters from a string
[s.upper() for s in "Hello World"]
# ['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']
# Strip off any commas from the end of strings in a list
[w.strip(',') for w in ['these,', 'words,,', 'mostly', 'have,commas,']]
# ['these', 'words', 'mostly', 'have,commas']
# Organize letters in words more reasonably - in an alphabetical order
sentence = "Beautiful is better than ugly"
["".join(sorted(word, key = lambda x: x.lower())) for word in sentence.split()]
# ['aBefiltuu', 'is', 'beertt', 'ahnt', 'gluy']
еще
else
может использоваться в конструкциях понимания List, но будьте осторожны относительно синтаксиса. Предложения if / else должны использоваться до цикла for
, а не после:
# create a list of characters in apple, replacing non vowels with '*'
# Ex - 'apple' --> ['a', '*', '*', '*' ,'e']
[x for x in 'apple' if x in 'aeiou' else '*']
#SyntaxError: invalid syntax
# When using if/else together use them before the loop
[x if x in 'aeiou' else '*' for x in 'apple']
#['a', '*', '*', '*', 'e']
Обратите внимание, что это использует другую конструкцию языка, условное выражение , которое само по себе не является частью синтаксиса понимания . Принимая во внимание, что if
после того, как for…in
является частью понимания списков и используется для фильтрации элементов из исходного итерабельного.
Двойная итерация
Порядок двойной итерации [... for x in ... for y in ...]
является либо естественным, либо контринтуитивным. Правило заключается в том , чтобы следовать эквивалент for
цикла:
def foo(i):
return i, i + 0.5
for i in range(3):
for x in foo(i):
yield str(x)
Это становится:
[str(x)
for i in range(3)
for x in foo(i)
]
Это можно сжать в одну строку как [str(x) for i in range(3) for x in foo(i)]
Мутация на месте и другие побочные эффекты
Перед использованием списка понимания, понять разницу между функциями , вызываемыми для их побочных эффектов (Mutating, или в месте функции) , которые , как правило , не возвращают None
, и функции , которые возвращают интересное значение.
Многие функции (особенно чистые функции) просто берут объект и возвращают некоторый объект. Функция in-place изменяет существующий объект, который называется побочным эффектом . Другие примеры включают операции ввода и вывода, такие как печать.
list.sort()
сортирует список на месте (что означает, что он изменяет исходный список) и возвращает значение None
. Поэтому он не будет работать, как ожидалось, в понимании списка:
[x.sort() for x in [[2, 1], [4, 3], [0, 1]]]
# [None, None, None]
Вместо этого sorted()
возвращает отсортированный list
а не сортировку на месте:
[sorted(x) for x in [[2, 1], [4, 3], [0, 1]]]
# [[1, 2], [3, 4], [0, 1]]
Возможно использование понятий для побочных эффектов, таких как функции ввода-вывода или функции на месте. Однако цикл for обычно более читабельен. Хотя это работает в Python 3:
[print(x) for x in (1, 2, 3)]
Вместо этого используйте:
for x in (1, 2, 3):
print(x)
В некоторых ситуациях, побочные функции эффекта подходят для списка понимания. random.randrange()
имеет побочный эффект изменения состояния генератора случайных чисел, но он также возвращает интересное значение. Кроме того, next()
можно вызывать на итераторе.
Следующий генератор случайных величин не является чистым, но имеет смысл, когда случайный генератор сбрасывается каждый раз, когда выражение оценивается:
from random import randrange
[randrange(1, 7) for _ in range(10)]
# [2, 3, 2, 1, 1, 5, 2, 4, 3, 5]
Пробелы в списках
Более сложные проверки списков могут достигать нежелательной длины или становиться менее читаемыми. Хотя это менее распространено в примерах, можно разбить понимание списка на несколько строк следующим образом:
[
x for x
in 'foo'
if x not in 'bar'
]
Словарь
Понимание словаря аналогично пониманию списка, за исключением того, что он создает объект словаря вместо списка.
Основной пример:
{x: x * x for x in (1, 2, 3, 4)}
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
что является еще одним способом написания:
dict((x, x * x) for x in (1, 2, 3, 4))
# Out: {1: 1, 2: 4, 3: 9, 4: 16}
Как и в случае со списком, мы можем использовать условное утверждение внутри понимания dict, чтобы создать только элементы dict, удовлетворяющие некоторому критерию.
{name: len(name) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6}
# Out: {'Exchange': 8, 'Overflow': 8}
Или, переписанный с использованием выражения генератора.
dict((name, len(name)) for name in ('Stack', 'Overflow', 'Exchange') if len(name) > 6)
# Out: {'Exchange': 8, 'Overflow': 8}
Начиная со словаря и использования словарного знака в качестве фильтра пары ключ-значение
initial_dict = {'x': 1, 'y': 2}
{key: value for key, value in initial_dict.items() if key == 'x'}
# Out: {'x': 1}
Клавиша переключения и значение словаря (инвертированный словарь)
Если у вас есть указатель, содержащий простые значения хеширования (дублирующиеся значения могут иметь неожиданные результаты):
my_dict = {1: 'a', 2: 'b', 3: 'c'}
и вы хотели поменять клавиши и значения, вы можете использовать несколько подходов в зависимости от стиля кодирования:
-
swapped = {v: k for k, v in my_dict.items()}
-
swapped = dict((v, k) for k, v in my_dict.iteritems())
-
swapped = dict(zip(my_dict.values(), my_dict))
-
swapped = dict(zip(my_dict.values(), my_dict.keys()))
-
swapped = dict(map(reversed, my_dict.items()))
print(swapped)
# Out: {a: 1, b: 2, c: 3}
Если ваш словарь большой, подумайте об импорте itertools и используйте izip
или imap
.
Слияние словарей
Объедините словари и, возможно, переопределите старые значения с вложенным пониманием словаря.
dict1 = {'w': 1, 'x': 1}
dict2 = {'x': 2, 'y': 2, 'z': 2}
{k: v for d in [dict1, dict2] for k, v in d.items()}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Тем не менее, распаковка словарей ( PEP 448 ) может быть предпочтительной.
{**dict1, **dict2}
# Out: {'w': 1, 'x': 2, 'y': 2, 'z': 2}
Примечание : в Python 3.0 были добавлены словарные словари и были добавлены в версии 2.0+, в отличие от списков, которые были добавлены в версии 2.0. Версии <2.7 могут использовать выражения генератора и встроенный dict()
для имитации поведения понимания словаря.
Выражения генератора
Выражения генератора очень похожи на списки. Основное различие заключается в том, что он не создает сразу полный набор результатов; он создает объект-генератор, который затем может быть повторен.
Например, см. Разницу в следующем коде:
# list comprehension
[x**2 for x in range(10)]
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# generator comprehension
(x**2 for x in xrange(10))
# Output: <generator object <genexpr> at 0x11b4b7c80>
Это два очень разных объекта:
представление списка возвращает объект
list
тогда как генераторное понимание возвращаетgenerator
.объекты
generator
не могут быть проиндексированы и используютnext
функцию для упорядочивания элементов.
Примечание . Мы используем xrange
так как он также создает объект-генератор. Если мы будем использовать диапазон, будет создан список. Кроме того, xrange
существует только в более поздней версии python 2. В python 3 range
просто возвращает генератор. Дополнительные сведения см. В разделе « Различия между диапазонами и примерами функций xrange» .
g = (x**2 for x in xrange(10))
print(g[0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object has no attribute '__getitem__'
g.next() # 0
g.next() # 1
g.next() # 4
...
g.next() # 81
g.next() # Throws StopIteration Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
ПРИМЕЧАНИЕ. Функция
g.next()
должна быть замененаnext(g)
иxrange
сrange
посколькуIterator.next()
иxrange()
не существуют в Python 3.
Хотя оба они могут быть повторены аналогичным образом:
for i in [x**2 for x in range(10)]:
print(i)
"""
Out:
0
1
4
...
81
"""
for i in (x**2 for x in xrange(10)):
print(i)
"""
Out:
0
1
4
.
.
.
81
"""
Случаи применения
Выражения генератора оцениваются лениво, что означает, что они генерируют и возвращают каждое значение только при повторении генератора. Это часто бывает полезно при повторении с помощью больших наборов данных, избегая необходимости создания дубликата набора данных в памяти:
for square in (x**2 for x in range(1000000)):
#do something
Другой распространенный вариант использования - избегать повторения по целому итерабельному, если это не является необходимым. В этом примере элемент извлекается из удаленного API с каждой итерацией get_objects()
. Тысячи объектов могут существовать, их необходимо извлекать один за другим, и нам нужно знать только, существует ли объект, соответствующий шаблону. Используя выражение генератора, когда мы сталкиваемся с объектом, соответствующим шаблону.
def get_objects():
"""Gets objects from an API one by one"""
while True:
yield get_next_item()
def object_matches_pattern(obj):
# perform potentially complex calculation
return matches_pattern
def right_item_exists():
items = (object_matched_pattern(each) for each in get_objects())
for item in items:
if item.is_the_right_one:
return True
return False
Установить понимание
Установление понимания аналогично пониманию списка и словаря , но оно создает набор , который представляет собой неупорядоченный набор уникальных элементов.
# A set containing every value in range(5):
{x for x in range(5)}
# Out: {0, 1, 2, 3, 4}
# A set of even numbers between 1 and 10:
{x for x in range(1, 11) if x % 2 == 0}
# Out: {2, 4, 6, 8, 10}
# Unique alphabetic characters in a string of text:
text = "When in the Course of human events it becomes necessary for one people..."
{ch.lower() for ch in text if ch.isalpha()}
# Out: set(['a', 'c', 'b', 'e', 'f', 'i', 'h', 'm', 'l', 'o',
# 'n', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y'])
Имейте в виду, что наборы неупорядочены. Это означает, что порядок результатов в наборе может отличаться от порядка, представленного в приведенных выше примерах.
Примечание . Установить понимание доступно, поскольку python 2.7+, в отличие от системных списков, которые были добавлены в версии 2.0. В Python 2.2 до Python 2.6 функция set()
может использоваться с выражением-генератором для получения того же результата:
set(x for x in range(5))
# Out: {0, 1, 2, 3, 4}
Избегайте повторяющихся и дорогостоящих операций с использованием условной оговорки
Рассмотрим нижеприведенное понимание:
>>> def f(x):
... import time
... time.sleep(.1) # Simulate expensive function
... return x**2
>>> [f(x) for x in range(1000) if f(x) > 10]
[16, 25, 36, ...]
Это приводит к двум вызовам f(x)
для 1000 значений x
: один вызов для генерации значения, а другой для проверки условия if
. Если f(x)
является особенно дорогостоящей операцией, это может иметь значительные последствия для производительности. Хуже того, если вызов f()
имеет побочные эффекты, он может иметь неожиданные результаты.
Вместо этого вы должны оценивать дорогостоящую операцию только один раз для каждого значения x
, создавая промежуточное итерируемое ( выражение генератора ) следующим образом:
>>> [v for v in (f(x) for x in range(1000)) if v > 10]
[16, 25, 36, ...]
Или, используя встроенный эквивалент карты :
>>> [v for v in map(f, range(1000)) if v > 10]
[16, 25, 36, ...]
Другим способом, который может привести к более читаемому коду, является поместить частичный результат ( v
в предыдущем примере) в итерируемый (например, список или кортеж), а затем перебрать его. Поскольку v
будет единственным элементом в итерабельном, результат состоит в том, что теперь мы имеем ссылку на вывод нашей медленной функции, вычисленной только один раз:
>>> [v for x in range(1000) for v in [f(x)] if v > 10]
[16, 25, 36, ...]
Однако на практике логика кода может быть более сложной, и важно сохранить ее читабельным. В общем случае для сложного однострочного слоя рекомендуется отдельная функция генератора :
>>> def process_prime_numbers(iterable):
... for x in iterable:
... if is_prime(x):
... yield f(x)
...
>>> [x for x in process_prime_numbers(range(1000)) if x > 10]
[11, 13, 17, 19, ...]
Еще один способ предотвратить вычисление f(x)
несколько раз - использовать декоратор @functools.lru_cache()
(Python 3.2+) на f(x)
. Таким образом, поскольку вывод f
для входа x
уже был вычислен один раз, второй вызов функции для первоначального понимания списка будет таким же быстрым, как поиск словаря. Этот подход использует memoization для повышения эффективности, что сопоставимо с использованием выражений генератора.
Скажем, вы должны сгладить список
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
Некоторые из методов могут быть следующими:
reduce(lambda x, y: x+y, l)
sum(l, [])
list(itertools.chain(*l))
Однако понимание списков обеспечит наилучшую временную сложность.
[item for sublist in l for item in sublist]
Ярлыки, основанные на + (включая подразумеваемое использование в сумме), являются, по необходимости, O (L ^ 2), когда есть L sublists - поскольку промежуточный список результатов продолжает увеличиваться, на каждом шаге появляется новый объект промежуточного результата выделено, и все элементы предыдущего промежуточного результата должны быть скопированы (а также несколько новых добавленных в конце). Таким образом (для простоты и без фактической потери общности) скажем, что у вас есть L подсписок из I предметов каждый: первые предметы I копируются взад и вперед L-1 раз, второй I - L-2 раза и т. Д .; общее количество копий I умножает сумму x для x от 1 до L, т. е. I * (L ** 2) / 2.
Понимание списка просто генерирует один список, один раз и копирует каждый элемент (от его первоначального места жительства до списка результатов) также ровно один раз.
Понимание, связанное с кортежами
Предложение for
для определения списка может содержать более одной переменной:
[x + y for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [3, 7, 11]
[x + y for x, y in zip([1, 3, 5], [2, 4, 6])]
# Out: [3, 7, 11]
Это так же , как регулярные for
петель:
for x, y in [(1,2), (3,4), (5,6)]:
print(x+y)
# 3
# 7
# 11
Обратите внимание, однако, если выражение, которое начинает понимать, является кортежем, тогда оно должно быть заключено в скобки:
[x, y for x, y in [(1, 2), (3, 4), (5, 6)]]
# SyntaxError: invalid syntax
[(x, y) for x, y in [(1, 2), (3, 4), (5, 6)]]
# Out: [(1, 2), (3, 4), (5, 6)]
Подсчет событий с использованием понимания
Когда мы хотим подсчитать количество элементов в итерабельном, которые удовлетворяют некоторому условию, мы можем использовать понимание для создания идиоматического синтаксиса:
# Count the numbers in `range(1000)` that are even and contain the digit `9`:
print (sum(
1 for x in range(1000)
if x % 2 == 0 and
'9' in str(x)
))
# Out: 95
Основную концепцию можно обобщить следующим образом:
- Итерации по элементам в
range(1000)
. - Сцепить все необходимое ,
if
условия. - Используйте 1 как выражение, чтобы вернуть 1 для каждого элемента, который соответствует условиям.
- Суммируйте все
1
с для определения количества элементов, соответствующих условиям.
Примечание . Здесь мы не собираем 1
с в списке (обратите внимание на отсутствие квадратных скобок), но мы передаем их непосредственно sum
функции, суммирующей их. Это называется выражением генератора , которое аналогично пониманию.
Изменение типов в списке
Количественные данные часто считываются как строки, которые перед обработкой необходимо преобразовать в числовые типы. Типы всех элементов списка могут быть преобразованы либо с помощью функции « Пояснение списка», либо с помощью функции map()
.
# Convert a list of strings to integers.
items = ["1","2","3","4"]
[int(item) for item in items]
# Out: [1, 2, 3, 4]
# Convert a list of strings to float.
items = ["1","2","3","4"]
map(float, items)
# Out:[1.0, 2.0, 3.0, 4.0]