Книга: Как устроен Python. Гид для разработчиков, программистов и интересующихся
Назад: 13. Условия и отступы
Дальше: 15. Итерации

14. Контейнеры: списки, кортежи и множества

Многие типы, рассматривавшиеся до настоящего момента, были скалярными величинами, содержащими одно значение. Целые числа, числа с плавающей точкой и логические значения — все это скалярные значения.

Контейнеры способны содержать не один, а несколько объектов (скалярные типы и даже другие контейнеры). В этой главе рассматриваются некоторые из таких типов — списки, кортежи и множества.

14.1. Списки

Списки, как следует из самого названия, используются для хранения списков объектов. В Python список может содержать элементы произвольного типа, в том числе и элементы разных типов. Впрочем, на практике в списках чаще хранятся элементы только одного типа. Список также можно представить себе как упорядоченную последовательность элементов. Списки относятся к изменяемым типам; это означает, что вы можете добавлять, удалять и изменять их содержимое без создания нового объекта. Существует два способа создания пустых списков; вызовом класса list и с использованием синтаксиса литералов в квадратных скобках — [ ]:

>>> names = list()

>>> other_names = []

Чтобы список при создании уже был заполнен, перечислите нужные значения в квадратных скобках с использованием синтаксиса литералов:

>>> other_names = ['Fred', 'Charles']

ПРИМЕЧАНИЕ

Класс list также может использоваться для создания заранее заполненных списков, но синтаксис получается довольно громоздким, потому что ему должен передаваться список:

>>> other_names = list(['Fred', 'Charles'])

Обычно этот класс используется для объединения других типов последовательностей в списки. Например, строка является последовательностью символов. Если передать строку при вызове list, вы получите список отдельных символов:

>>> list('Matt')

['M', 'a', 't', 't']

 

У списков, как и у других типов, имеются методы, которые можно для них вызывать (чтобы просмотреть полный перечень методов, используйте вызов dir([])). Например, добавление элементов в конец списка осуществляется методом .append:

>>> names.append('Matt')

>>> names.append('Fred')

>>> print(names)

['Matt', 'Fred']

Помните, что списки являются изменяемыми. Python не возвращает новый список при присоединении элементов. Обратите внимание: вызов .append не возвращает список (REPL ничего не выводит). Вместо этого возвращается None, а список обновляется на месте. В языке Python функция или метод по умолчанию возвращает None. Невозможно создать метод, который ничего не возвращает.

14.2. Индексы

Список — один из типов последовательностей в языке Python. В последовательностях хранятся упорядоченные наборы объектов. Чтобы эффективно работать с последовательностями, необходимо понимать, что такое индекс. С каждым элементом списка связан индекс, описывающий его местоположение в списке. Например, при изготовлении картофельных чипсов используется картофель, масло и соль, которые обычно перечисляются именно в таком порядке. Картофель находится на первом месте списка, масло на втором, а соль на третьем.

Во многих языках программирования первому элементу последовательности присваивается индекс 0, второму — индекс 1, третьему — индекс 2, и т.д. Нумерация индексов начинается с нуля.

Для обращения к элементу списка указывается индекс этого элемента, заключенный в квадратные скобки:

>>> names[0]

'Matt'

 

>>> names[1]

'Fred'

14.3. Вставка в список

Чтобы вставить элемент в позицию с определенным индексом, используйте метод .insert. При вызове .insert все элементы, следующие за этим индексом, сдвигаются вправо:

>>> names.insert(0, 'George')

>>> print(names)

['George', 'Matt', 'Fred']

Для замены элемента с заданным индексом применяется синтаксис с квадратными скобками:

>>> names[1] = 'Henry'

>>> print(names)

['George', 'Henry', 'Fred']

Чтобы присоединить элементы в конец списка, воспользуйтесь методом .append:

>>> names.append('Paul')

>>> print(names)

['George', 'Henry', 'Fred', 'Paul']

ПРИМЕЧАНИЕ

В Python списки в действительности реализуются в виде массива указателей. Такая реализация обеспечивает быстрый произвольный доступ к элементам по индексам. Кроме того, операции присоединения и удаления в конце списка выполняются быстро (O(1)), тогда как операции вставки и удаления в середине списка выполняются медленнее (O(n)). Если окажется, что вам часто приходится вставлять и извлекать элементы в начале списка, лучше использовать структуру данных collections.deque.

14.4. Удаление из списка

Для удаления элементов из списка используется метод .remove:

>>> names.remove('Paul')

>>> print(names)

['George', 'Henry', 'Fred']

Также возможно удаление по индексу с использованием синтаксиса с квадратными скобками:

>>> del names[1]

>>> print(names)

['George', 'Fred']

14.5. Сортировка списков

Одна из самых распространенных операций со списками — сортировка. Метод .sort упорядочивает значения в списке, при этом сортировка осуществляется «на месте». Метод не возвращает новую отсортированную копию списка, а обновляет список с измененным порядком элементов:

>>> names.sort()

>>> print(names)

['Fred', 'George']

Если предыдущий порядок элементов важен, скопируйте список перед сортировкой. Впрочем, для сортировки последовательностей также можно воспользоваться более общим решением с функцией sorted. Функция sorted работает с любой последовательностью и возвращает новый список с упорядоченными элементами:

>>> old = [5, 3, -2, 1]

>>> nums_sorted = sorted(old)

>>> print(nums_sorted)

[-2, 1, 3, 5]

>>> print(old)

[5, 3, -2, 1]

Будьте внимательны с тем, что вы сортируете. Python требует, чтобы вы явно выражали свои намерения. В Python 3 при попытке отсортировать список с разнотипными элементами может произойти ошибка:

>>> things = [2, 'abc', 'Zebra', '1']

>>> things.sort()

Traceback (most recent call last):

...

TypeError: unorderable types: str() < int()

Как метод .sort, так и функция sorted позволяют управлять сортировкой, для чего в параметре key передается произвольная функция сортировки. Параметр key может содержать функцию (а также класс или метод), которая получает один элемент и возвращает нечто пригодное для сравнения.

В следующем примере, где в параметре key передается str, все элементы списка сортируются по правилам сортировки строк:

>>> things.sort(key=str)

>>> print(things)

['1', 2, 'Zebra', 'abc']

14.6. Полезные советы по работе со списками

Обычно списки также поддерживают другие методы. Попробуйте открыть интерпретатор Python и ввести несколько примеров. Не забывайте о своих друзьях dir и help.

СОВЕТ

Встроенная функция range строит целочисленные последовательности. Следующий фрагмент создает последовательность из чисел от 0 до 4:

>>> nums = range(5)

>>> nums

range(5)

Python 3 не любит лишней работы. Функция range не материализует список, а предоставляет итератор, который будет возвращать эти числа при переборе. Передав результат list, вы сможете увидеть сгенерированные числа:

>>> list(nums)

[0, 1, 2, 3, 4]

Обратите внимание: сгенерированная последовательность не включает число 5. Многие функции Python, использующие конечные индексы, означают «до, но не включая» (другой пример такого рода — срезы — будет представлен позднее).

Если последовательность должна начинаться с ненулевого числа, функции range можно передать два параметра. В этом случае первый параметр определяет начало последовательности (включительно), а второй — конец (не включая):

# Числа от 2 до 5

>>> nums2 = range(2, 6)

>>> nums2

range(2, 6)

У функции range также имеется необязательный третий параметр — приращение. Если приращение равно 1 (по умолчанию), следующее число в последовательности, возвращаемой range, будет на 1 больше предыдущего. С приращением 2 будет возвращаться каждое второе число. В следующем примере возвращаются только четные числа, меньшие 11:

>>> even = range(0, 11, 2)

>>> even

range(0, 11, 2)

 

>>> list(even)

[0, 2, 4, 6, 8, 10]

 

514445.png 

Рис. 14.1. Сортировка списка методом .sort. Помните, что список сортируется «на месте». В результате вызова метода .sort список изменяется, а сам метод возвращает None

514476.png 

Рис. 14.2. Сортировка списка функцией sorted. Обратите внимание: список не изменяется, а при вызове возвращается новый список. Кроме того, Python повторно использует элементы списка, не создавая новых элементов

514510.png 

Рис. 14.3. На последнем этапе в результате операции присваивания переменная указывает на новый список. Обратите внимание: функция sorted работает с любыми последовательностями, не только со списками

ПРИМЕЧАНИЕ

Конструкция «до, но не включая» более формально называется полуоткрытым интервалом. Обычно полуоткрытые интервалы используются при определении последовательностей натуральных чисел. Они обладают рядом удобных свойств:

• Длина серии последовательных чисел равна разности между концом и началом интервала.

• Сращивание двух подпоследовательностей обходится без перекрытия.

В Python идиома полуоткрытого интервала применяется достаточно часто. Пример:

>>> a = range(0, 5)

>>> b = range(5, 10)

>>> both = list(a) + list(b)

>>> len(both) # 10 - 0

10

 

>>> both

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

Если вы часто работаете с числовыми последовательностями, вам стоит поступать так же — особенно при определении API.

Знаменитый теоретик в области программирования Эдгар Дейкстра (Edsger Dijkstra) размышлял об индексировании с 0 и о том, почему этот вариант правилен. Он завершает свои рассуждения так:

«Многие языки программирования проектировались без должного внимания к этой подробности».

К счастью, Python к числу таких языков не относится.

14.7. Кортежи

Кортежи (tuples) представляют собой неизменяемые последовательности. Их можно рассматривать как упорядоченные записи данных. После того как кортеж будет создан, изменить его не удастся. Чтобы создать кортеж с использованием синтаксиса литерала, заключите компоненты в круглые скобки и разделите их запятыми. Также имеется класс tuple, который может использоваться для построения нового кортежа из существующей последовательности:

>>> row = ('George', 'Guitar')

>>> row

('George', 'Guitar')

 

>>> row2 = ('Paul', 'Bass')

>>> row2

('Paul', 'Bass')

Можно создать кортеж с нулем или с одним элементом. На практике кортежи используются для хранения записей данных, поэтому такой вариант встречается нечасто. Есть два способа создания пустых кортежей — функция tuple и синтаксис литералов:

>>> empty = tuple()

>>> empty

()

 

>>> empty = ()

>>> empty

()

Кортеж с одним элементом можно создать тремя способами:

>>> one = tuple([1])

>>> one

(1,)

 

>>> one = (1,)

>>> one

(1,)

 

>>> one = 1,

>>> one

(1,)

 

ПРИМЕЧАНИЕ

Круглые скобки используются в Python для обозначения вызовов функций или методов. Кроме того, они используются для определения приоритета операторов и еще могут использоваться при создании кортежей. Такое обилие применений может привести к недоразумениям. Запомните простое правило: если в круглые скобки заключен один элемент, то Python рассматривает круглые скобки как обычные (для определения приоритета операторов) — например, как в записи (2 + 3) * 8. Если же в скобки заключено несколько элементов, разделенных запятыми, Python рассматривает их как кортеж:

>>> d = (3)

>>> type(d)

<class 'int'>

На первый взгляд может показаться, что d — кортеж, заключенный в круглые скобки, но Python считает d целым числом. Для кортежей, состоящих из одного элемента, следует поставить после элемента запятую (,) или же использовать класс tuple со списком, состоящим из одного элемента:

>>> e = (3,)

>>> type(e)

<class 'tuple'>

 

Создать кортеж с несколькими элементами можно тремя способами. Обычно последний считается наиболее соответствующим стилю Python. Благодаря наличию круглых скобок проще увидеть, что перед вами кортеж:

>>> p = tuple(['Steph', 'Curry', 'Guard'])

>>> p

('Steph', 'Curry', 'Guard')

 

>>> p = 'Steph', 'Curry', 'Guard'

>>> p

('Steph', 'Curry', 'Guard')

 

>>> p = ('Steph', 'Curry', 'Guard')

>>> p

('Steph', 'Curry', 'Guard')

Так как кортежи являются неизменяемыми, присоединение к ним новых элементов невозможно:

>>> p.append('Golden State')

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AttributeError: 'tuple' object has no attribute

'append'

ПРИМЕЧАНИЕ

Почему кортежи и списки существуют по отдельности? Почему бы просто не использовать списки, которые на первый взгляд являются надмножеством кортежей?

Главное различие между объектами заключается в изменяемости. Так как кортежи неизменяемы, они могут служить ключами в словарях. Кортежи часто используются для представления записей данных — например, строк запросов баз данных, которые могут содержать разнородные типы объектов. Например, в кортеже могут храниться имя, адрес и возраст:

>>> person = ('Matt', '123 North 456 East', 24)

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

Наконец, кортежи используют меньше памяти, чем списки. Если вы работаете с последовательностями, которые не должны изменяться, возможно, вам стоит воспользоваться кортежами ради экономии памяти.

14.8. Множества

Еще одну разновидность контейнеров в Python составляют множества (set). Множество представляет собой неупорядоченную совокупность объектов, в которой не может быть дубликатов. Как и кортеж, множество можно создать на базе списка или других последовательностей, элементы которых можно перебирать. Однако, в отличие от списков и кортежей, для множеств неважен порядок элементов. Множества часто используются для двух целей: для удаления дубликатов и для проверки принадлежности. Так как механизм поиска основан на оптимизированной функции хеширования, реализованной для словарей, операция поиска занимает очень мало времени даже для очень больших множеств.

ПРИМЕЧАНИЕ

Так как множества должны вычислять хеш-код для каждого элемента, в них могут храниться только хешируемые элементы. Хеш-код представляет собой квазиуникальное число, сгенерированное для заданного объекта. Если объект является хешируемым, для него всегда генерируется одно и то же число.

В языке Python изменяемые элементы не являются хешируемыми. Это означает, что хешировать список или словарь невозможно. Для хеширования пользовательских классов необходимо реализовать методы __hash__ и __eq__.

Множество можно определить передачей последовательности классу set (еще один класс преобразования, который выглядит как функция):

>>> digits = [0, 1, 1, 2, 3, 4, 5, 6,

... 7, 8, 9]

>>> digit_set = set(digits) # Удаление лишней 1

Множества также могут создаваться в синтаксисе литералов с { }:

>>> digit_set = {0, 1, 1, 2, 3, 4, 5, 6,

... 7, 8, 9}

Как упоминалось выше, множество отлично подходит для удаления дубликатов. При создании множества на базе последовательности все ­дубликаты удаляются. Так, из digit_set был удален лишний элемент 1:

>>> digit_set

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

Для проверки принадлежности используется операция in:

>>> 9 in digit_set

True

 

>>> 42 in digit_set

False

ПРИМЕЧАНИЕ

В Python существует особый протокол принадлежности. Если класс (множество, список или класс, определяемый пользователем) реализует метод __contains__ (или протокол перебора), он может использоваться для проверки принадлежности. Вследствие особенностей реализации множеств проверка принадлежности для них может выполняться намного быстрее, чем проверки по списку.

В следующем примере создается множество с именем odd. Оно будет использоваться в дальнейших примерах:

>>> odd = {1, 3, 5, 7, 9}

Множества Python поддерживают классические операции теории множеств, такие как объединение (|), пересечение (&), вычитание (-) и исключающее ИЛИ (^).

Оператор вычитания (-) удаляет элементы, входящие в одно множество, из другого множества:

>>> odd = {1, 3, 5, 7, 9}

 

# Вычитание

>>> even = digit_set - odd

>>> even

{0, 8, 2, 4, 6}

Обратите внимание на порядок элементов результата: на первый взгляд он кажется произвольным. Если для вас важен порядок элементов, вместо множества лучше использовать другой тип данных.

Операция пересечения (&) возвращает элементы, присутствующие в обоих множествах:

>>> prime = set([2, 3, 5, 7])

 

# В обоих множествах

>>> prime_even = prime & even

>>> prime_even

{2}

Операция объединения (|) возвращает множество, состоящее из всех элементов обоих множеств (с исключением дубликатов):

>>> numbers = odd | even

>>> print(numbers)

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

Операция исключающего ИЛИ (^) возвращает множество с элементами, присутствующими только в одном из двух множеств:

>>> first_five = set([0, 1, 2, 3, 4])

>>> two_to_six = set([2, 3, 4, 5, 6])

>>> in_one = first_five ^ two_to_six

>>> print(in_one)

{0, 1, 5, 6}

СОВЕТ

Когда стоит использовать множество вместо списка? Вспомните, что множества оптимизированы для проверки принадлежности и удаления дубликатов. Если вы выполняете операции объединения или вычитания списков, возможно, вам стоит вместо списков использовать множества.

Множества также намного эффективнее работают при проверке принадлежности. Оператор in для множеств работает быстрее, чем для списков. Тем не менее за эту скорость приходится расплачиваться: в отличие от списков и кортежей, множества не поддерживают конкретный порядок хранения элементов. Если для вас важен порядок элементов, используйте структуру данных, в которой он сохраняется.

14.9. Итоги

В этой главе описаны некоторые встроенные типы-контейнеры. Сначала были рассмотрены списки — изменяемые упорядоченные последовательности. Помните, что методы списков не возвращают новый список, а обычно изменяют список «на месте». Списки поддерживают вставку произвольных элементов, но обычно в списке хранятся однотипные элементы.

Затем были рассмотрены кортежи. Кортежи тоже упорядочены, как и списки, но в отличие от списков кортежи не поддерживают изменение «на месте». На практике они обычно используются для представления записей данных — например, строк, прочитанных из базы данных. В кортежах, представляющих записи данных, могут храниться разные типы объектов.

В завершение главы были представлены множества. Множества являются изменяемыми, но они не упорядочены. Они используются для удаления дубликатов и проверки принадлежности. Из-за используемого механизма хеширования операции с множествами выполняются быстро и эффективно. Для этого элементы множества должны быть хешируемыми.

14.10. Упражнения

1. Создайте список, сохраните в нем имена ваших коллег и друзей. Изменился ли идентификатор списка? Отсортируйте список. Какой элемент стоит в начале списка? Какой элемент является вторым?

2. Создайте кортеж с вашим именем, фамилией и возрастом. Создайте список people и присоедините к нему свой кортеж. Создайте другие кортежи с соответствующей информацией о ваших друзьях и присоедините их к списку. Отсортируйте список. Когда в книге будут рассмотрены функции, вы сможете использовать параметр key для сортировки списка по любому полю кортежа: имени, фамилии или возрасту.

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

4. Посетите сайт проекта «Гутенберг» и найдите страницу текста из Шекспира. Вставьте текст в строку, заключенную в тройные кавычки. Создайте другую строку с абзацем текста из Ральфа Уолдо Эмерсона. Используйте метод .split строк для получения списка слов из каждого текста. При помощи операций с множествами найдите общие слова, встречающиеся в текстах обоих авторов, а также слова, уникальные для каждого автора.

5. Кортежи и списки похожи, но обладают разным поведением. Используйте операции с множествами для нахождения атрибутов объекта списка, отсутствующих у объекта кортежа.

/

Назад: 13. Условия и отступы
Дальше: 15. Итерации