Python 3. Последовательности и множества

В 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 .