Рассматриваются кортежи и списки, а также новые типы коллекций, включая словари и множества.
Последовательности – это один из типов данных, поддерживающих
оператор проверки на вхождение (in
), функцию определения размера
(len()
), оператор извлечения срезов ([]
) и возможность выполнения
итераций. В языке Python имеется пять встроенных типов
последовательностей: bytearray
, bytes
, list
, str
и
tuple
. Ряд дополнительных типов последовательностей реализован в
стандартной библиотеке; наиболее примечательным
из них является тип collections.namedtuple
. При выполнении итераций
все эти последовательности гарантируют строго определенный порядок
следования элементов.
Строки мы уже рассматривали выше, а в этом разделе познакомимся с кортежами, именованными кортежами и списками.
Кортеж – это упорядоченная последовательность из нуля или более ссылок
на объекты. Кортежи поддерживают тот же синтаксис получения срезов,
что и строки. Это упрощает извлечение элементов из кортежа. Подобно
строкам, кортежи относятся к категории неизменяемых объектов, поэтому
мы не можем замещать или удалять какие-либо их элементы. Если нам
необходимо иметь возможность изменять упорядоченную
последовательность, то вместо кортежей можно просто использовать
списки или, если в программе уже используется кортеж, который
нежелательно модифицировать, можно преобразовать кортеж в список с
помощью функции преобразования list()
и затем изменять полученный
список.
Тип данных tuple
может вызываться как функция tuple()
– без
аргументов она возвращает пустой кортеж, с аргументом типа tuple
возвращает поверхностную копию аргумента; в случае, если аргумент
имеет другой тип, выполняется попытка преобразовать его в объект
типа tuple
. Эта функция принимает не более одного аргумента. Кроме
того, кортежи могут создаваться без использования функции
tuple()
. Пустой кортеж создается с помощью пары пустых круглых
скобок ()
, а кортеж, состоящий из одного или более элементов, может быть
создан с помощью запятых. Иногда кортежи приходится заключать в
круглые скобки, чтобы избежать синтаксической
неоднозначности. Например, чтобы передать кортеж 1, 2, 3
в функцию,
необходимо использовать такую форму записи: function((1, 2, 3))
.
Рисунок 1: Позиции элементов в кортеже
На рис. 1 показан кортеж
t = "venus", – 28, "green", "21", 19.74
и индексы элементов внутри кортежа. Строки
индексируются точно так же, но, если в строках каждой позиции
соответствует единственный символ, то в кортежах каждой позиции
соответствует единственная ссылка на объект.
Кортежи предоставляют всего два метода: t.count(x)
, который
возвращает количество объектов x
в кортеже t
, и t.index(x)
,
который возвращает индекс самого первого (слева) вхождения объекта x
в кортеж t
или возбуждает исключение ValueError
, если объект x
отсутствует в кортеже. (Эти методы имеются также и у списков.)
Кроме того, кортежи могут использоваться с оператором +
(конкатенации), *
(дублирования) и []
(получения среза), а
операторы in
и not in
могут применяться для проверки на
вхождение. Можно использовать также комбинированные операторы
присваивания +=
и *=
. Несмотря на то, что кортежи являются
неизменяемыми объектами, при выполнении этих операторов интерпретатор
Python создает за кулисами новый кортеж с результатом операции и
присваивает ссылку на него объекту, расположенному слева от оператора,
то есть используется тот же самый прием, что и со строками. Кортежи
могут сравниваться с помощью стандартных операторов сравнения (<
,
<=
, ==
, !=
, >=
, >
), при этом сравнивание производится
поэлементно (и рекурсивно, при наличии вложенных элементов, таких как
кортежи в кортежах).
Рассмотрим несколько примеров получения срезов, начав с извлечения единственного элемента и группы элементов:
>>> hair = "black", "brown", "blonde", "red"
>>> hair[2]
'blonde'
>>> hair[-3:] # то же, что и hair[1:]
('brown', 'blonde', 'red')
Эта операция выполняется точно так же, как и в случае со строками, списками или любыми другими последовательностями.
>>> hair[:2], "gray", hair[2:]
(('black', 'brown'), 'gray', ('blonde', 'red'))
Здесь мы попытались создать новый кортеж из 5 элементов, но в результате получили кортеж с тремя элементами, содержащий два двухэлементных кортежа. Это произошло потому, что мы применили оператор запятой к трем элементам (кортеж, строка и кортеж). Чтобы получить единый кортеж со всеми этими элементами, необходимо выполнить конкатенацию кортежей:
>>> hair[:2] + ("gray",) + hair[2:]
('black', 'brown', 'gray', 'blonde', 'red')
Чтобы создать кортеж из одного элемента, необходимо поставить запятую,
но если запятую просто добавить, будет получено исключение TypeError
(так как интерпретатор будет думать, что выполняется конкатенация
строки и кортежа), поэтому необходимо использовать запятую и круглые
скобки.
Коллекции допускают возможность вложения с любой глубиной
вложенности. Оператор извлечения срезов []
может применяться для
доступа к вложенным коллекциям столько раз, сколько это будет
необходимо. Например:
>>> things = (1, -7.5, ("pea", (5, "Xyz"), "queue"))
>>> things[2][1][1][2]
'z'
Кортежи могут хранить элементы любых типов, включая другие коллекции, такие как кортежи и списки, так как на самом деле кортежи хранят ссылки на объекты. Использование сложных, вложенных структур данных, таких, как показано ниже, легко может создавать путаницу. Одно из решений этой проблемы состоит в том, чтобы давать значениям индексов осмысленные имена. Например:
>>> MANUFACTURER, MODEL, SEATING = (0, 1, 2)
>>> MINIMUM, MAXIMUM = (0, 1)
>>> aircraft = ("Airbus", "A320-200", (100, 220))
>>> aircraft[SEATING][MAXIMUM]
220
В первых двух строках вышеприведенного фрагмента мы выполнили присваивание кортежам. Когда справа от оператора присваивания указывается последовательность (в данном случае – это кортежи), а слева указан кортеж, мы говорим, что последовательность справа распаковывается. Операция распаковывания последовательностей может использоваться для организации обмена значений между переменными, например:
a, b = (b, a)
Именованные кортежи ведут себя точно так же, как и обычные кортежи, и не уступают им в производительности. Отличаются они возможностью ссылаться на элементы кортежа не только по числовому индексу, но и по имени, что в свою очередь позволяет создавать сложные агрегаты из элементов данных.
В модуле collections
имеется функция namedtuple()
. Эта функция
используется для создания собственных типов кортежей. Например:
import collections
Sale = collections.namedtuple("Sale", "productid customerid date quantity price")
Первый аргумент функции collections.namedtuple()
– это имя
создаваемого кортежа. Второй аргумент – это строка имен, разделенных
пробелами, для каждого элемента, который будет присутствовать в этом
кортеже. Первый аргумент и имена во втором аргументе должны быть
допустимыми идентификаторами языка Python. Функция возвращает класс
(тип данных), который может использоваться для создания именованных
кортежей. Так, в примере выше мы можем интерпретировать имя Sale
как
имя любого другого класса (такого как tuple
) в языке Python и
создавать объекты типа Sale
. Например:
sales = []
sales.append(Sale(432, 921, "2008-09-14", 3, 7.99))
sales.append(Sale(419, 874, "2008-09-15", 1, 18.49))
В этом примере мы создали список из двух элементов типа Sale
, то
есть из двух именованных кортежей. Мы можем обращаться к элементам
таких кортежей по их индексам – например, обратиться к элементу
price
в первом элементе списка sales
можно с помощью выражения
sales[0][-1]
(вернет значение 7.99
) – или по именам, которые
делают программный код более удобочитаемым:
total = 0
for sale in sales:
total += sale.quantity*sale.price
print("Total {0:.2f}".format(total))
Очень часто простоту и удобство, которые предоставляют именованные
кортежи, можно обратить на пользу делу. Например, ниже приводится
версия примера aircraft
из предыдущего подраздела, имеющая более
аккуратный вид:
>>> Aircraft = collections.namedtuple("Aircraft", "manufacturer model seating")
>>> Seating = collections.namedtuple("Seating", "minimum maximum")
>>> aircraft = Aircraft("Airbus", "A320-200", Seating(100, 220))
>>> aircraft.seating.maximum
220
Список – это упорядоченная последовательность из нуля или более ссылок на объекты. Списки поддерживают тот же синтаксис получения срезов, что и строки с кортежами. Это упрощает извлечение элементов из списка. В отличие от строк и кортежей списки относятся к категории изменяемых объектов, поэтому мы можем замещать или удалять любые их элементы. Кроме того, существует возможность вставлять, замещать и удалять целые срезы списков.
Тип данных list
может вызываться как функция list()
–
без аргументов она возвращает пустой список, с аргументом типа list
возвращает поверхностную копию аргумента; в случае, если аргумент
имеет другой тип, выполняется попытка преобразовать его в объект типа
list
. Эта функция принимает не более одного аргумента. Кроме того,
списки могут создаваться без использования функции list()
. Пустой
список создается с помощью пары пустых квадратных скобок []
, а список,
состоящий из одного или более элементов, может быть создан с помощью
последовательности элементов, разделенных запятыми, заключенной в
квадратные скобки. Другой способ создания списков заключается в
использовании генераторов списков – эта тема будет рассматриваться
ниже в этом подразделе.
Поскольку все элементы списка в действительности являются ссылками на
объекты, списки, как и кортежи, могут хранить элементы любых типов
данных, включая коллекции, такие как списки и кортежи. Списки могут
сравниваться с помощью стандартных операторов сравнения (<
, <=
,
==
, !=
, >=
, >
), при этом сравнивание производится поэлементно
(и рекурсивно, при наличии вложенных элементов, таких как списки или
кортежи в списках).
В результате выполнения операции присваивания
L = [ – 17.5, "kilo", 49, "V", ["ram", 5, "echo"], 7]
мы получим список, как показано на рис. 2
Рисунок 2: Позиции элементов в списке
К спискам, таким как L
, мы можем применять оператор извлечения
среза, повторяя его столько раз, сколько потребуется для доступа к
элементам в списке, как показано ниже:
L[0] == L[-6] == -17.5
L[1] == L[-5] == 'kilo'
L[1][0] == L[-5][0] == 'k'
L[4][2] == L[4][-1] == L[-2][2] == L[-2][-1] == 'echo'
L[4][2][1] == L[4][2][-3] == L[-2][-1][1] == L[-2][-1][-3] == 'c'
Списки, как и кортежи, могут вкладываться друг в друга; допускают
выполнение итераций по их элементам и извлечение срезов. Все примеры с
кортежами, которые приводились в предыдущем подразделе, будут работать
точно так же, если вместо кортежей в них будут использованы
списки. Списки поддерживают операторы проверки на вхождение in
и
not in
, оператор конкатенации +
, оператор расширения +=
(то есть
добавляет операнд справа в конец списка) и операторы дублирования *
и *=
. Списки могут также использоваться в качестве аргументов
функции len()
.
Кроме того, списки предоставляют методы, перечисленные в табл. collections:seq:tbl:1
Таблица 1. Методы списков
Синтаксис | Описание |
L.append(x) | Добавляет элемент x в конец списка L |
L.count(x) | Возвращает число вхождений элемента x в список L |
L.extend(m) или L += m | Добавляет в конец списка L все элементы итерируемого объекта m |
L.index(x, start, end) | Возвращает индекс самого первого (слева) вхождения элемента x в список L (или в срез start:end списка L ); в противном случае возбуждает исключение ValueError |
L.insert(i, x) | Вставляет элемент x в список L в позицию int i |
L.pop() | Удаляет самый последний элемент из списка L и возвращает его в качестве результата |
L.pop(i) | Удаляет из списка L элемент с индексом int i и возвращает его в качестве результата |
L.remove(x) | Удаляет самый первый (слева) найденный элемент x из списка L или возбуждает исключение ValueError , если элемент x не будет найден ` |
L.reverse() | Переставляет в памяти элементы списка в обратном порядке |
L.sort() | Сортирует список в памяти. Этот метод принимает те же необязательные аргументы key и reverse что и встроенная функция sorted() |
Несмотря на то, что для доступа к элементам списка можно использовать
оператор извлечения среза, тем не менее в некоторых ситуациях бывает
необходимо одновременно извлечь две или более частей списка.
Сделать это можно с помощью операции распаковывания
последовательности. Любой итерируемый объект (списки, кортежи и
другие) может быть распакован с помощью оператора распаковывания
«звездочка» (*
). Когда слева от оператора присваивания указывается
две или более переменных, одна из которых предваряется символом *
,
каждой переменной присваивается по одному элементу списка, а
переменной со звездочкой присваивается оставшаяся часть списка. Ниже
приводится несколько примеров выполнения распаковывания списков:
>>> first, *rest = [9, 2, -4, 8, 7]
>>> first, rest
(9, [2, -4, 8, 7])
>>> first, *mid, last = "Charles Philip Arthur George Windsor".split()
>>> first, mid, last
('Charles', ['Philip', 'Arthur', 'George'], 'Windsor')
>>> *directories, executable = "/usr/local/bin/gvim".split("/")
>>> directories, executable
(['', 'usr', 'local', 'bin'], 'gvim')
Когда используется оператор распаковывания последовательности,
как в данном примере, выражение *rest
и подобные ему называются
выражениями со звездочкой.
В языке Python имеется также похожее понятие аргументов со звездочкой. Например, допустим, что имеется следующая функция, принимающая три аргумента:
def product(a, b, c):
return a * b * c
тогда мы можем вызывать эту функцию с тремя аргументами или использовать аргументы со звездочкой:
>>> product(2, 3, 5)
30
>>> L = [2, 3, 5]
>>> product(*L)
30
>>> product(2, *L[1:])
30
В первом примере функция вызывается, как обычно, с тремя
аргументами. Во втором вызове использован аргумент со звездочкой; в
этом случае список из трех элементов распаковывается оператором *
,
так что функция получает столько аргументов, сколько ей
требуется. Того же эффекта можно было бы добиться при использовании
кортежа с тремя элементами. В третьем вызове функции первый аргумент
передается традиционным способом, а другие два – посредством
применения операции распаковывания двухэлементного среза списка L
.
Имеется возможность выполнять итерации по элементам списка с помощью
конструкции for item in L:
. Если в цикле потребуется изменять
элементы списка, то можно использовать следующий прием:
for i in range(len(L)):
L[i] = process(L[i])
Встроенная функция range()
возвращает целочисленный
итератор. С одним целочисленным аргументом, n
, итератор range()
возвращает последовательность чисел \( 0 \), \( 1 \), ..., \( n-1 \).
Этот прием можно использовать для увеличения всех элементов в списке целых чисел. Например:
for i in range(len(numbers)):
numbers[i] += 1
Небольшие списки часто создаются как литералы, но длинные списки
обычно создаются программным способом. Списки целых чисел могут
создаваться с помощью выражения list(range(n))
; когда необходим
итератор целых чисел, достаточно функции range()
; а для создания
списков других типов часто используется оператор цикла for ... in
.
Предположим, например, что нам требуется получить список високосных
годов в определенном диапазоне. Для начала мы могли бы использовать
такой цикл:
leaps = []
for year in range(1900, 1940):
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
leaps.append(year)
Когда функции range()
передаются два целочисленных
аргумента n
и m
, итератор возвращает последовательность целых
чисел n
, n+1
, ..., m–1
.
Конечно, если диапазон известен заранее, можно было
бы использовать литерал списка, например,
leaps = [1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936]
.
Генератор списков – это выражение и цикл с дополнительным условием, заключенное в квадратные скобки, в котором цикл используется для создания элементов списка, а условие используется для исключения нежелательных элементов. В простейшем виде генератор списков записывается, как показано ниже:
[item for item in iterable]
Это выражение вернет список всех элементов объекта iterable
и
семантически ничем не отличается от выражения
list(iterable)
. Интересными генераторы списков делают две
особенности – они могут использоваться как выражения и они допускают
включение условной инструкции, вследствие чего мы получаем две
типичные синтаксические конструкции использования генераторов списков:
[expression for item in iterable]
[expression for item in iterable if condition]
Вторая форма записи эквивалентна циклу:
temp = []
for item in iterable:
if condition:
temp.append(expression)
Обычно выражение expression
является либо самим элементом item
,
либо некоторым выражением с его участием. Конечно, генератору
списков не требуется временная переменная temp[]
, которая необходима
в версии с циклом for ... in
.
Теперь можно переписать программный код создания списка високосных годов с использованием генератора списка. Мы сделаем это в три этапа. Сначала создадим список, содержащий все годы в указанном диапазоне:
leaps = [y for y in range(1900, 1940)]
То же самое можно было бы сделать с помощью выражения
leaps = list(range(1900, 1940))
. Теперь добавим простое условие,
которое будет оставлять в списке только каждый четвертый год:
leaps = [y for y in range(1900, 1940) if y % 4 == 0]
И, наконец, получаем окончательную версию:
leaps = [y for y in range(1900, 1940)
if (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)]
Использование генератора списков в данном случае позволило уменьшить объем программного кода с четырех строк до двух – не так много, но в крупных проектах суммарная экономия может оказаться весьма существенной.
Так как генераторы списков воспроизводят списки, то есть итерируемые
объекты, и сами генераторы списков используют итерируемые объекты,
имеется возможность вкладывать генераторы списков друг
в друга. Это эквивалентно вложению циклов for ... in
. Например, если
бы нам потребовалось сгенерировать список всех возможных кодов одежды
для разных полов, разных размеров и расцветок, но исключая одежду для
полных женщин, нужды и чаянья которых индустрия моды нередко
игнорирует, мы могли бы использовать вложенные циклы
for ... in
, как показано ниже:
codes = []
for sex in "MF":
for size in "SMLX":
if sex == "F" and size
continue
for color in "BGW":
codes.append(sex + size + color)
Этот фрагмент воспроизводит список, содержащий 21 элемент – ['MSB',
'MSG', ..., 'FLW']
. Тот же самый список можно создать парой строк,
если воспользоваться генераторами списков:
codes = [s + z + c for s in "MF" for z in "SMLX" for c in "BGW"
if not (s == "F" and z == "X")]
Здесь каждый элемент списка воспроизводится выражением s + z + c
.
Кроме того, в генераторе списков несколько иначе построена логика
обхода нежелательной комбинации пол/размер – проверка выполняется в
самом внутреннем цикле, тогда как в версии с циклами for ... in
эта
проверка выполняется в среднем цикле. Любой генератор списков можно
переписать, используя один или более циклов for ... in
.