Кортежи в Python: полное руководство с методами и примерами использования
Разбираемся с кортежами в Python — неизменяемость, упаковка и распаковка, именованные кортежи, методы count() и index() — с рабочими примерами и советами, когда выбирать кортеж вместо списка.
Кортеж — это встроенный в Python неизменяемый тип последовательности. На первый взгляд он похож на список, но именно неизменяемость определяет, как его используют: как запись из связанных данных, ключ словаря или возвращаемое значение функции. В этом руководстве разберём всё о кортежах: создание, распаковку, оба метода кортежей и то, когда стоит выбрать кортеж вместо списка.
Что такое кортеж
Кортеж — это упорядоченная неизменяемая коллекция элементов. «Упорядоченная» означает, что элементы сохраняют своё положение и их можно индексировать; «неизменяемая» — что после создания кортежа нельзя добавить, удалить или заменить его элементы на месте.
1
2
3
4
5
point = (3, 4)
colors = "red", "green", "blue" # parentheses are optional
empty = ()
single = (5,) # trailing comma required!
not_a_tuple = (5) # this is just the int 5
Ловушка с кортежем из одного элемента рано или поздно подводит почти каждого: (5) вычисляется как выражение в скобках, а не как кортеж. Python распознаёт кортеж по запятой, а не по скобкам — 5, сам по себе уже является полноценным кортежем.
Кортежи также можно строить с помощью конструктора tuple(), который принимает любой итерируемый объект:
1
2
3
tuple([1, 2, 3]) # (1, 2, 3)
tuple("abc") # ('a', 'b', 'c')
tuple(range(3)) # (0, 1, 2)
Неизменяемость — что она гарантирует, а что нет
Нельзя переприсвоить элемент или изменить размер кортежа:
1
2
t = (1, 2, 3)
t[0] = 99 # TypeError: 'tuple' object does not support item assignment
Но неизменяемость — поверхностная. Если кортеж содержит изменяемый объект, этот объект по-прежнему можно изменить на месте:
1
2
3
4
t = ([1, 2], "fixed")
t[0].append(3) # allowed — the list inside the tuple changed
print(t) # ([1, 2, 3], 'fixed')
# t[0] = [9, 9] # still a TypeError — you can't replace the reference
Таким образом, кортеж гарантирует, что не изменится его собственная структура — количество ячеек и то, на какой объект указывает каждая из них, — но не гарантирует, что содержимое изменяемого элемента останется замороженным.
Индексирование, срезы и итерация
Кортежи поддерживают тот же синтаксис индексирования и срезов, что и списки:
1
2
3
4
5
6
7
8
9
10
t = (10, 20, 30, 40, 50)
t[0] # 10
t[-1] # 50
t[1:4] # (20, 30, 40)
t[::-1] # (50, 40, 30, 20, 10)
for item in t:
print(item)
30 in t # True
Распаковка
Распаковка присваивает элементы кортежа отдельным переменным в одну строку — пожалуй, самая частая причина, по которой кортежи встречаются в повседневном коде.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
point = (3, 4)
x, y = point
print(x, y) # 3 4
# Starred unpacking grabs the rest into a list
first, *rest = (1, 2, 3, 4)
print(first, rest) # 1 [2, 3, 4]
first, *middle, last = (1, 2, 3, 4, 5)
print(middle) # [2, 3, 4]
# Swapping without a temp variable
a, b = 1, 2
a, b = b, a
Возврат нескольких значений
Функции, которые «возвращают несколько значений», на самом деле возвращают один кортеж:
1
2
3
4
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([4, 1, 9, 2])
Кортежи как ключи словаря
Поскольку кортежи неизменяемы (и хешируемы, если хешируем каждый их элемент), их можно использовать как ключи словаря или элементы множества — то, чего списки никогда не позволяют:
1
2
3
4
5
distances = {
("New York", "Boston"): 215,
("New York", "Chicago"): 790,
}
distances[("New York", "Boston")] # 215
Такой приём часто встречается при кэшировании результатов функций по нескольким аргументам или при работе с координатами сетки вида (row, col).
Методы кортежей
Поскольку кортежи неизменяемы, у них гораздо меньше методов, чем у списков — нет append, remove, sort или insert. Существуют только два метода, и оба лишь читают данные, не изменяя их.
tuple.count()
1
tuple.count(value)
Возвращает, сколько раз value встречается в кортеже.
1
2
3
t = (1, 2, 2, 3, 2, 4)
t.count(2) # 3
t.count(5) # 0 — no error, just zero
Ловушка: count() сравнивает элементы через ==, поэтому 1, 1.0 и True считаются равными:
1
(1, True, 1.0).count(1) # 3
tuple.index() — как найти индекс элемента в кортеже
Метод index() — это именно тот инструмент, который позволяет найти индекс элемента в кортеже. Индексирование кортежа по позиции (t[0]) работает в обратную сторону — от индекса к значению; метод index(), наоборот, идёт от значения к индексу.
1
tuple.index(value, start=0, end=len(tuple))
Возвращает индекс первого вхождения value. Необязательные аргументы start/end сужают диапазон поиска — точно так же, как границы в срезах.
1
2
3
t = (10, 20, 30, 20, 40)
t.index(20) # 1 — first match only
t.index(20, 2) # 3 — search starting from index 2
Ловушка: в отличие от count(), метод index() вызывает ValueError, если значение не найдено — он не возвращает -1 или None. Если значение может отсутствовать, всегда проверяйте его через in или оборачивайте вызов в try/except:
1
2
3
4
if 99 in t:
idx = t.index(99)
else:
idx = None
Кортежи против списков
| Кортеж | Список | |
|---|---|---|
| Изменяемость | Неизменяемый | Изменяемый |
| Синтаксис | (1, 2, 3) | [1, 2, 3] |
| Методы | только count(), index() | Полный набор: append, sort, remove и другие |
| Хешируемость | Да (если хешируемы все элементы) | Нет |
| Использование как ключа словаря / элемента множества | Да | Нет |
| Производительность | Немного быстрее создаётся и перебирается | Немного больше накладных расходов |
| Типичное применение | Фиксированные записи — координаты, значения RGB, возвращаемые значения функций | Растущие/изменяющиеся коллекции |
| Смысловое назначение | «Эта структура не изменится» | «Эта коллекция может измениться» |
Разница в производительности реальна, но невелика: кортежи не резервируют лишнюю память впрок, как это делают списки ради дешёвого append(), поэтому они занимают чуть меньше памяти и создаются чуть быстрее. Это приятный бонус, но настоящая причина выбирать кортеж — сама гарантия неизменяемости: она явно фиксирует намерение и защищает от случайного изменения данных, которые изменяться не должны, например фиксированной формы возвращаемого значения функции или записи, прочитанной из файла.
Именованные кортежи
Обычные кортежи позиционны — point[0] и point[1] сами по себе ничего не говорят о своём смысле. collections.namedtuple даёт полям кортежа имена, сохраняя при этом его производительность и неизменяемость:
1
2
3
4
5
6
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x, p.y # 3 4
p[0], p[1] # 3 4 — still indexable like a regular tuple
Для кода с проверкой типов typing.NamedTuple предлагает ту же идею, но с аннотациями типов:
1
2
3
4
5
6
7
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
p = Point(3, 4)
Оба варианта под капотом остаются обычными кортежами — isinstance(p, tuple) возвращает True, — поэтому всё, о чём шла речь в этом руководстве, включая count() и index(), применимо и к ним.
Частые приёмы и подводные камни
Несколько практических привычек упрощают повседневную работу с кортежами.
Используйте распаковку кортежей в циклах при переборе пар, например результатов dict.items() или enumerate():
1
2
3
4
5
6
scores = {"alice": 90, "bob": 85}
for name, score in scores.items():
print(name, score)
for index, value in enumerate(["a", "b", "c"]):
print(index, value)
Конкатенация и повторение работают так же, как у списков, но всегда создают новый кортеж, поскольку исходные изменить на месте нельзя:
1
2
3
4
a = (1, 2)
b = (3, 4)
a + b # (1, 2, 3, 4) — new tuple
a * 3 # (1, 2, 1, 2, 1, 2) — new tuple
Преобразование между кортежами и списками часто нужно, когда нужно сначала собрать данные (список), а затем зафиксировать их (кортеж), или наоборот:
1
2
3
4
t = (1, 2, 3)
lst = list(t) # [1, 2, 3] — now mutable
lst.append(4)
t2 = tuple(lst) # (1, 2, 3, 4) — frozen again
Вложенные кортежи распаковываются рекурсивно, что удобно для структурированных записей:
1
2
record = ("Alice", (1990, 5, 12))
name, (year, month, day) = record
Остерегайтесь случайных кортежей из-за лишней запятой. Это самая распространённая ошибка, связанная с кортежами:
1
2
3
4
5
def get_value():
return 5, # oops — returns (5,), not 5
x = get_value()
print(x) # (5,)
Если функция должна возвращать одно значение, всегда проверяйте, нет ли случайной запятой в конце строки return.
Часто задаваемые вопросы
Быстрее ли кортежи, чем списки? Незначительно — при создании и переборе, потому что Python не нужно резервировать дополнительную ёмкость под будущий рост. Разница редко имеет значение, если только вы не создаёте миллионы объектов в горячем цикле — выбирайте кортеж или список исходя прежде всего из семантики изменяемости, а не производительности.
Можно ли отсортировать кортеж? Не на месте — метода t.sort() не существует. Используйте встроенную функцию sorted(), которая возвращает новый список: sorted(t). Если результат нужен обратно в виде кортежа, оберните его: tuple(sorted(t)).
Почему (1, 2) == [1, 2] возвращает False? При сравнении Python учитывает не только содержимое, но и тип. Кортеж и список с одинаковыми элементами никогда не считаются равными, даже несмотря на то, что оба поддерживают индексирование и итерацию одинаковым образом.
Как найти индекс элемента в кортеже? Вызовите метод кортежа index(): t.index(value). Он возвращает позицию первого совпадения и вызывает ValueError, если значения нет в кортеже, поэтому сначала стоит проверить его наличие через in. Чтобы искать в определённой части кортежа, передайте start/end: t.index(value, 2).
Какие индексы кортежа допустимы? Те же, что и для списков: от 0 до len(t) - 1, а также отрицательные индексы, которые отсчитываются с конца (t[-1] — последний элемент). Обращение к индексу за пределами этого диапазона вызывает IndexError.
Могут ли кортежи находиться внутри множеств? Да, если каждый элемент внутри кортежа сам по себе хешируем. Например, кортеж, содержащий список, не хешируем и не может быть помещён в множество или использован как ключ словаря.
Похожие материалы
- Изменяемый аналог всего, о чём говорилось здесь, разобран в полном руководстве по спискам в Python.
tuple()— лишь одна из многих встроенных функций, которые стоит знать; см. справочник по встроенным функциям Python.- Кортежи постоянно встречаются в ML-конвейерах для записей фиксированной формы вроде
(features, label)— реальные примеры смотрите в статье Feature Engineering для табличного ML.
