Кортежи (tuple) в Python представляют собой последовательности, схожие со списками, но они не подвержены изменению. Как в списках, в кортеже можно размещать элементы различных типов и они разделяются запятыми, но вместо квадратных скобок используются круглые.
>>> a = (10, 2.13, "square", 89, "C") >>> a (10, 2.13, 'square', 89, 'C')
Кортеж позволяет извлекать элементы и осуществлять срезы:
>>> a[3] 89 >>> a[1:3] (2.13, 'square')
Но изменение элементов невозможно:
>>> a[0] = 11 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
Тип tuple лишен методов для модификации состава элементов.
Зачем же использовать кортежи, если они только имитируют списки без возможности их редактирования? Ответ прост: кортежи позволяют защитить данные от изменений. Если понадобится преобразовать кортеж в список и обратно, то в Python предусмотрены функции list() и tuple():
>>> a = (10, 2.13, "square", 89, "C") >>> b = [1, 2, 3] >>> c = list(a) >>> d = tuple(b) >>> c [10, 2.13, 'square', 89, 'C'] >>> d (1, 2, 3)
Теперь о том, где кортежи могут быть особенно полезны: при передаче изменяемых объектов в функцию в Python используется механизм передачи по ссылке. Это подразумевает, что не происходит создание копии объекта, а передается ссылка на существующий объект. Если объект в функции изменяется, это затрагивает и исходный объект в глобальной области.
def addNum(seq, num): for i in range(len(seq)): seq[i] += num return seq origin = [3, 6, 2, 6] changed = addNum(origin, 3) print(origin) print(changed)
В этом коде содержится логическая ошибка: несмотря на отсутствие исключений, первоначальный список изменяется. Параметр seq хранит не копию локальную, а ссылку на оригинал. Поэтому возврат его из функции теряет смысл. Если функция намеренно вносит изменения в глобальный список, её можно переписать так:
def addNum(seq, num): for i in range(len(seq)): seq[i] += num origin = [3, 6, 2, 6] addNum(origin, 3) print(origin)
Как поступить, если необходим новый список на основе имеющегося, но без изменений оригинала? Можно создать копию внутри функции и возвратить её:
def addNum(seq, num): new_seq = [] for i in seq: new_seq.append(i + num) return new_seq origin = [3, 6, 2, 6] changed = addNum(origin, 3) print(origin) print(changed)
Таким образом, оригинал остается без изменений, а элементы лишь добавляются в новую структуру.
Ещё один подход - использование кортежей, ведь в больших приложениях проверить, что ни одна функция не меняет глобальные данные, непросто.
Лучше с самого начала сделать глобальный список кортежем. Ведь неизменяемые объекты передаются по значению, и передаваемая в функцию структура - это именно копия. Даже если вы случайно передадите оригинал, изменить его содержимое невозможно. Для манипуляций понадобится создать копию или изменить тип, создав новую локальную структуру.
def addNum(seq, num): seq = list(seq) for i in range(len(seq)): seq[i] += num return seq origin = (3, 6, 2, 6) changed = addNum(origin, 3) print(origin) print(changed)
Списки в кортежах
Кортежи могут содержать списки, подобно тому как списки могут содержать другие списки.
>>> nested = (1, "do", ["param", 10, 20])
Можем ли мы изменить список ["param", 10, 20], который встроен в кортеж nested? Отвечаем утвердительно. Хотя сам кортеж неизменяем, вложенный список изменяем.
>>> nested[2][1] = 15 >>> nested (1, 'do', ['param', 15, 20])
Индексация вида nested[2][1] помогает получать доступ к вложенным объектам: первый индекс указывает на позицию внутри кортежа, второй - на конкретный элемент внутри объекта. Так список внутри кортежа имеет индекс 2, а число 10 в списке - индекс 1.
Выражение довольно странное: кортеж неприступен для изменений, однако можно изменять то, что в него вложено. На деле, мы не меняем сам кортеж, а его ссылку на объект.
Для наглядности изменим пример:
>>> l = ["param", 10, 20] >>> t = (1, "do", l) >>> t (1, 'do', ['param', 10, 20])
Кортеж содержит ссылку на список. Сама ссылка неизменна, но изменять содержимое списка можно:
>>> l.pop(0)
'param'
>>> t
(1, 'do', [10, 20])
При попытке использовать этот трюк с неизменяемыми типами результатов не будет:
>>> a = "Kat" >>> t = (a, l) >>> t ('Kat', [10, 20]) >>> a = "Bat" >>> t ('Kat', [10, 20])
Неизменяемые типы передаются в кортеж аналогично параметрам в функцию – по значению. Это значит, что их значения копируются.
Практическая работа
- Чтобы избежать изменений в оригинальном списке, можно не прибегать к кортежу. Копия создается через метод copy() или срезом [:]. Проделайте копирование и убедитесь, что изменения не касаются оригинала.
- Создайте кортеж с десятью случайными числами от 0 до 5 включительно. На заполнение второго кортежа используйте числа от -5 до 0. Придумайте функцию для этой задачи. Объедините кортежи с помощью +, формируя третий. Применяя метод count(), найдите количество нулей в третьем кортеже и выведите его вместе с количеством нулей.