Рассматриваются кортежи и списки, а также новые типы коллекций, включая словари и множества.

Последовательности

Последовательности – это один из типов данных, поддерживающих оператор проверки на вхождение (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.