← Назад к вопросам

Почему кортеж быстрее списка?

2.0 Middle🔥 131 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кортежи быстрее списков благодаря нескольким факторам

Это связано с тем, что кортежи неизменяемы (immutable), что позволяет Python'у производить оптимизации, которые невозможны для изменяемых списков. Рассмотрим основные причины.

1. Отсутствие операций выделения памяти при изменении

Списки изменяемы, поэтому Python должен предусмотреть место для возможного расширения:

import sys

my_list = [1, 2, 3]
my_tuple = (1, 2, 3)

print(sys.getsizeof(my_list))   # ~56 байт (с запасом для расширения)
print(sys.getsizeof(my_tuple))  # ~40 байт (ровно столько, сколько нужно)

При создании списка Python резервирует дополнительное место для возможных append() операций. Кортеж просто занимает ровно столько памяти, сколько ему нужно.

2. Хеширование и использование как ключей

Кортежи хешируемы (hashable), поэтому их можно использовать как ключи словарей и элементы множеств. Это позволяет быстрее осуществлять поиск:

# Кортежи можно использовать как ключи
coordinates = {(0, 0): "origin", (1, 1): "diagonal"}

# Списки нельзя использовать как ключи
try:
    data = {[1, 2]: "value"}  # TypeError
except TypeError as e:
    print(f"Ошибка: {e}")

# Кортежи можно добавлять в множества
coords_set = {(0, 0), (1, 1), (2, 2)}
print((0, 0) in coords_set)  # Быстрый поиск O(1)

3. Оптимизация интерпретатором CPython

Поскольку кортежи неизменяемы, интерпретатор может применить специальные оптимизации, такие как интерниования малых кортежей:

a = (1, 2, 3)
b = (1, 2, 3)

print(a is b)  # True в CPython (кортежи кешируются)

# Со списками такого не происходит
c = [1, 2, 3]
d = [1, 2, 3]
print(c is d)  # False (разные объекты)

4. Отсутствие проверок на изменение при итерации

При итерации по списку Python должен проверять, не был ли он изменен, чтобы избежать ошибок. Кортежи такой проверки не требуют:

# С кортежом итерация быстрее
my_tuple = (1, 2, 3, 4, 5)
for item in my_tuple:
    print(item)  # Никаких проверок на изменение

# Со списком есть скрытые проверки
my_list = [1, 2, 3, 4, 5]
for item in my_list:
    print(item)  # Python проверяет, не изменился ли список

5. Сборка мусора (Garbage Collection)

Кортежи, будучи неизменяемыми, требуют меньше внимания сборщику мусора. Они могут жить дольше и требуют меньше отслеживания:

import timeit

# Создание и итерация по кортежу
tuple_time = timeit.timeit(
    "for i in t: pass",
    "t = (1, 2, 3, 4, 5)",
    number=1000000
)

# Создание и итерация по списку
list_time = timeit.timeit(
    "for i in l: pass",
    "l = [1, 2, 3, 4, 5]",
    number=1000000
)

print(f"Кортеж: {tuple_time:.4f}")
print(f"Список: {list_time:.4f}")
# Кортеж обычно быстрее на 10-20%

6. Создание кортежей в CPython более эффективно

В Python существует специальная инструкция LOAD_CONST для загрузки константных кортежей, которая очень быстрая. Для списков этого нет:

import dis

def with_tuple():
    return (1, 2, 3)

def with_list():
    return [1, 2, 3]

print("Кортеж:")
dis.dis(with_tuple)

print("\nСписок:")
dis.dis(with_list)

Кортеж создается с помощью LOAD_CONST (очень быстро), а список требует BUILD_LIST (медленнее).

7. Отсутствие операций вроде append, extend, insert

Списки поддерживают операции изменения, которые имеют накладные расходы. Кортежи этого не делают:

# Список имеет много методов для изменения
print(dir([]))
# append, extend, insert, remove, pop, clear, sort, reverse

# Кортеж имеет только методы для чтения
print(dir(()))  # count, index

Практический пример производительности

import timeit

# Сравнение времени создания
setup_tuple = "t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)"
setup_list = "l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"

# Доступ по индексу
print("Доступ [0]:")
print(f"  Кортеж: {timeit.timeit('t[0]', setup_tuple, number=10000000):.4f}")
print(f"  Список: {timeit.timeit('l[0]', setup_list, number=10000000):.4f}")

# Итерация
print("Итерация:")
print(f"  Кортеж: {timeit.timeit('for i in t: pass', setup_tuple, number=10000000):.4f}")
print(f"  Список: {timeit.timeit('for i in l: pass', setup_list, number=10000000):.4f}")

Когда использовать кортежи?

  • Как ключи словарей — списки не могут быть ключами
  • В множествах — списки не могут быть элементами множеств
  • Как константы — когда гарантировано, что данные не изменятся
  • При передаче между функциями — для защиты от случайного изменения
  • В критичных по производительности местах — где каждая миллисекунда важна

Вывод

Кортежи быстрее списков потому, что неизменяемость позволяет Python применять оптимизации на уровне памяти, хеширования и интерпретации байт-кода. Разница может быть небольшой (10-20%), но в критичных по производительности местах это имеет значение. Однако выбор между кортежом и списком всегда должен основываться на семантике — нужны ли изменения или нет.