Post

Кортежи в Python: полное руководство с методами и примерами использования

Разбираемся с кортежами в Python — неизменяемость, упаковка и распаковка, именованные кортежи, методы count() и index() — с рабочими примерами и советами, когда выбирать кортеж вместо списка.

Кортежи в Python: полное руководство с методами и примерами использования
🚀 Find Better AI Tools Faster Submit Your AI Tool & Reach Thousands of Builders Get Started →

Кортеж — это встроенный в 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.

Могут ли кортежи находиться внутри множеств? Да, если каждый элемент внутри кортежа сам по себе хешируем. Например, кортеж, содержащий список, не хешируем и не может быть помещён в множество или использован как ключ словаря.

Похожие материалы

Khushal Jethava
Khushal Jethava

Machine Learning Engineer at Codiste, specializing in Generative AI, NLP, and Computer Vision. Building production AI systems with Python.

🚀 Find Better AI Tools Faster Submit Your AI Tool & Reach Thousands of Builders Get Started →
This post is licensed under CC BY 4.0 by the author.
🚀 Find Better AI Tools Faster Submit Your AI Tool & Reach Thousands of Builders Get Started →
🚀 Find Better AI Tools Faster Submit Your AI Tool & Reach Thousands of Builders Get Started →