Python 3. Последовательности и множества
Введение
Обычно в переменной мы храним одно значение: число или строку. Но очень часто нужно хранить сразу много значений: список серверов, набор пользователей, настройки программы. Для этого в Python 3 есть типы данных, которые умеют хранить в себе несколько объектов.
В этой статье поговорим о пяти таких типах:
- Строки (
str) — неизменяемые последовательности символов. Изменить строку нельзя, можно только создать новую. Символы в строке пронумерованы слева направо, нумерация начинается с нуля. Обычно строки используют для хранения текста. - Списки (
list) — изменяемые последовательности объектов. В одном списке можно хранить объекты разных типов, например числа и строки одновременно. Списки допускают вложенность: внутри списка может лежать другой список. Объекты в списке нумеруются так же, как символы в строке. - Кортежи (
tuple) — неизменяемые последовательности объектов. Это что-то среднее между строкой и списком: как и список, кортеж хранит любые объекты, но как и строку, его нельзя изменить. Если нужен «изменённый» кортеж, то создаётся новый. - Словари (
dict) — объекты, в которых данные хранятся парами «ключ — значение». Значение достаём не по номеру, а по ключу. Словари тоже допускают вложенность. Начиная с Python 3.7 словари запоминают порядок, в котором в них добавляли элементы. - Множества (
set) — изменяемые коллекции объектов без дубликатов и без порядка. Мы можем хранить в множестве разные объекты, но они не пронумерованы, и два одинаковых объекта в множестве не хранятся. Зато поиск в множестве выполняется очень быстро, даже если оно большое.
Небольшое уточнение по терминам. Строго говоря, последовательности (sequences) в Python — это строки, списки и кортежи (а ещё range и bytes, но о них в другой раз). Их объединяет то, что элементы идут друг за другом и у каждого есть номер. Словарь относится к другой категории — это отображение (mapping), а множество — отдельный тип коллекции. Но все они хранят много объектов сразу, поэтому рассматривать их удобно вместе.
Статья актуальна для современных версий Python 3 (3.10 и новее, примеры проверялись на 3.13). Если вы используете версию старее 3.7, то порядок элементов в словарях вам гарантирован не будет.
Создание объектов
Каждый из этих типов можно создать двумя способами: литералом (то есть просто записав значение) или с помощью класса. На практике почти всегда используют литералы, они короче и читаются проще.
Строки:
my_str1 = str('Строка 1')
my_str2 = 'Строка 2'
Списки:
my_list1 = list([1, 2, 'три'])
my_list2 = [1, 2, 'три']
Кортежи:
my_tuple1 = tuple([1, 2, 'три'])
my_tuple2 = (1, 2, 'три')
Словари:
my_dict1 = dict({1: 'Иван', 2: 'Иванов'})
my_dict2 = {1: 'Иван', 2: 'Иванов'}
Множества:
my_set1 = set([1, 2, 'три'])
my_set2 = {1, 2, 'три'}
Обратите внимание:
- Списки создаются квадратными скобками
[], кортежи — круглыми(), а словари и множества — фигурными{}. Отличить словарь от множества просто: в словаре есть парыключ: значение, а в множестве только значения. - Когда мы создаём объект через класс, то в скобках передаём любую последовательность, из которой этот объект будет собран. Поэтому
list([1, 2, 'три'])иtuple([1, 2, 'три'])создаются почти одинаково, меняется только класс.
Проверить тип объекта можно функцией type():
print(type(my_list2))
print(type(my_dict2))
Вывод:
<class 'list'>
<class 'dict'>
Пустые объекты и кортеж из одного элемента
Есть два места, где новички часто ошибаются.
Пустое множество. Если написать {}, то получится не множество, а пустой словарь. Пустое множество создаётся только через класс:
empty_dict = {}
empty_set = set()
print(type(empty_dict))
print(type(empty_set))
Вывод:
<class 'dict'>
<class 'set'>
Кортеж из одного элемента. Кортеж создают не круглые скобки, а запятые. Скобки нужны только для читаемости. Поэтому для кортежа из одного элемента запятая обязательна:
not_tuple = (1)
my_tuple = (1,)
print(type(not_tuple))
print(type(my_tuple))
Вывод:
<class 'int'>
<class 'tuple'>
Доступ к элементам
Строки, списки и кортежи — по номеру (индексу)
Эти три типа — последовательности, поэтому элементы в них достаются по индексу. Индексация начинается с нуля. Отрицательные индексы отсчитываются с конца: -1 — последний элемент.
my_str = 'Привет'
my_list = [1, 2, 'три']
my_tuple = (1, 2, 'три')
print(my_str[0])
print(my_str[-1])
print(my_list[2])
print(my_tuple[1])
Вывод:
П
т
три
2
Ещё можно брать срезы — то есть несколько элементов сразу: [от:до], где элемент с индексом «до» в срез не входит.
print(my_list[0:2])
print(my_str[1:4])
Вывод:
[1, 2]
рив
Словари — по ключу
В словаре у каждого значения есть ключ, по которому его и достают:
user = {'name': 'Иван', 'surname': 'Иванов'}
print(user['name'])
Вывод:
Иван
Если такого ключа нет, то будет ошибка KeyError. Чтобы её избежать, используют метод get(), он вернёт None (или значение, которое вы укажете вторым аргументом):
print(user.get('age'))
print(user.get('age', 'не указан'))
Вывод:
None
не указан
Множества — по индексу не достать
У множеств нет ни индексов, ни ключей, поэтому my_set[0] вызовет ошибку TypeError. Зато можно быстро проверить, есть ли объект в множестве:
my_set = {1, 2, 'три'}
print(2 in my_set)
print(5 in my_set)
Вывод:
True
False
Оператор in работает и со всеми остальными типами, но в множествах и словарях (по ключам) он работает намного быстрее, чем в списках, особенно когда элементов много.
Изменяемые и неизменяемые типы
Это самое важное отличие между типами. Давайте посмотрим на него на примерах.
Строка и кортеж изменить нельзя:
my_str = 'Привет'
my_str[0] = 'п'
Результат:
TypeError: 'str' object does not support item assignment
my_tuple = (1, 2, 'три')
my_tuple[0] = 100
Результат:
TypeError: 'tuple' object does not support item assignment
Если нужно «изменить» строку или кортеж, то мы создаём новый объект и записываем его в ту же переменную:
my_str = 'п' + my_str[1:]
my_tuple = my_tuple + (4,)
print(my_str)
print(my_tuple)
Вывод:
привет
(1, 2, 'три', 4)
Список, словарь и множество изменять можно:
my_list = [1, 2, 'три']
my_list[0] = 100
my_list.append(4)
print(my_list)
user = {'name': 'Иван'}
user['surname'] = 'Иванов'
print(user)
my_set = {1, 2}
my_set.add(3)
my_set.add(3)
print(my_set)
Вывод:
[100, 2, 'три', 4]
{'name': 'Иван', 'surname': 'Иванов'}
{1, 2, 3}
Обратите внимание на множество: тройку мы добавили дважды, но в множестве она осталась одна. Это удобно использовать, чтобы убрать дубликаты из списка:
numbers = [1, 2, 2, 3, 3, 3]
unique = list(set(numbers))
print(unique)
Вывод:
[1, 2, 3]
Порядок элементов после преобразования в множество не гарантируется, так что если порядок важен, то это не лучший способ.
Что можно хранить в множестве и в ключах словаря
Есть ограничение: элементы множества и ключи словаря должны быть хешируемыми. Простыми словами, это значит, что объект не должен меняться. Строки, числа и кортежи (если внутри них тоже нет изменяемых объектов) подходят. Списки, словари и множества — нет:
my_set = {[1, 2], 3}
Результат:
TypeError: unhashable type: 'list'
А вот значениями в словаре может быть что угодно, в том числе список или другой словарь.
Порядок элементов
- Строки, списки и кортежи упорядочены: у каждого элемента есть номер, и порядок не меняется сам по себе.
- Словари с версии Python 3.7 хранят элементы в том порядке, в котором мы их добавляли. Но доставать значения по номеру всё равно нельзя, только по ключу. В старых версиях (и в старых статьях, включая первую версию этой) словари называли неупорядоченными, сейчас это уже не так.
- Множества порядка не имеют. Не стоит рассчитывать на то, что элементы при выводе будут идти в том порядке, в котором вы их добавляли.
Множества и математические операции
Раз мы заговорили про множества, то стоит упомянуть, для чего они ещё полезны. С ними можно делать операции над наборами данных: объединение, пересечение и разность.
a = {1, 2, 3}
b = {3, 4}
print(a | b)
print(a & b)
print(a - b)
Вывод:
{1, 2, 3, 4}
{3}
{1, 2}
a | b— объединение, все элементы из обоих множеств;a & b— пересечение, только общие элементы;a - b— разность, элементы изa, которых нет вb.
Например, так можно быстро найти, каких серверов нет в одном списке по сравнению с другим.
Общие операции
Некоторые функции работают со всеми этими типами:
my_str = 'Привет'
my_list = [1, 2, 'три']
my_dict = {'name': 'Иван', 'surname': 'Иванов'}
my_set = {1, 2, 'три'}
print(len(my_str))
print(len(my_list))
print(len(my_dict))
print(len(my_set))
Вывод:
6
3
2
3
Перебирать элементы в цикле for тоже можно у всех типов:
for item in my_list:
print(item)
Вывод:
1
2
три
При переборе словаря по умолчанию мы получаем ключи.
Итог
Мы разобрали, как создаются в Python 3 последовательности и множества, как достать из них элементы и чем они отличаются друг от друга.
| Тип | Изменяемый | Упорядоченный | Дубликаты | Как достать элемент |
|---|---|---|---|---|
| Строка | Нет | Да | Да | По индексу |
| Список | Да | Да | Да | По индексу |
| Кортеж | Нет | Да | Да | По индексу |
| Словарь | Да | Да, по порядку добавления (с 3.7) | Ключи уникальны, значения — любые | По ключу |
| Множество | Да | Нет | Нет | Только проверить in |
Коротко, что выбирать:
- нужен текст — строка;
- нужен набор элементов, который будем менять — список;
- нужен набор элементов, который менять не должны — кортеж;
- нужны данные вида «название — значение» — словарь;
- нужен набор уникальных элементов или быстрая проверка «есть ли такой элемент» — множество.
Про каждый тип данных у меня есть отдельная статья:
Если понравилась статья, подпишись на мой канал в VK или Telegram .