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

Что меньше весит список или кортеж?

2.0 Middle🔥 161 комментариев
#Git и VCS#Базы данных (NoSQL)#Безопасность

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

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

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

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

Краткий ответ

Кортеж легче списка — он занимает меньше памяти из-за оптимизаций CPython. Но это не главное отличие. Главное — в их назначении: кортежи неизменяемы (immutable), списки изменяемы (mutable).

Сравнение памяти

Прямое измерение:

import sys

# Пустые контейнеры
empty_list = []
empty_tuple = ()

print(f"Empty list size: {sys.getsizeof(empty_list)} bytes")
# Output: Empty list size: 56 bytes

print(f"Empty tuple size: {sys.getsizeof(empty_tuple)} bytes")
# Output: Empty tuple size: 40 bytes

print(f"Разница: {sys.getsizeof(empty_list) - sys.getsizeof(empty_tuple)} bytes")
# Output: Разница: 16 bytes

Со значениями:

import sys

# Списки и кортежи с одинаковыми данными
my_list = [1, 2, 3, 4, 5]
my_tuple = (1, 2, 3, 4, 5)

print(f"List: {sys.getsizeof(my_list)} bytes")
# Output: List: 104 bytes

print(f"Tuple: {sys.getsizeof(my_tuple)} bytes")
# Output: Tuple: 80 bytes

print(f"Разница: {sys.getsizeof(my_list) - sys.getsizeof(my_tuple)} bytes")
# Output: Разница: 24 bytes

# Большие контейнеры
big_list = list(range(10000))
big_tuple = tuple(range(10000))

print(f"Big list: {sys.getsizeof(big_list)} bytes")
# Output: Big list: 87032 bytes

print(f"Big tuple: {sys.getsizeof(big_tuple)} bytes")
# Output: Big tuple: 80064 bytes

print(f"Разница: {sys.getsizeof(big_list) - sys.getsizeof(big_tuple)} bytes")
# Output: Разница: ~6968 bytes (~8%)

Почему кортеж легче?

1. Внутренняя структура

# Список (mutable) имеет дополнительные поля для:
# - текущий размер
# - выделенная ёмкость (для расширения)
# - ссылка на массив элементов

class ListStructure:
    def __init__(self):
        self.ob_refcnt = 1  # Счётчик ссылок
        self.ob_type = list  # Тип
        self.size = 0  # Количество элементов
        self.allocated = 0  # Выделенная ёмкость
        self.items = []  # Указатель на массив

# Кортеж (immutable) проще:
class TupleStructure:
    def __init__(self):
        self.ob_refcnt = 1  # Счётчик ссылок
        self.ob_type = tuple  # Тип
        self.size = 0  # Количество элементов
        # Данные хранятся прямо в структуре, нет лишних полей
        self.items = []  # Встроенный массив

2. Ёмкость для расширения (capacity)

Список выделяет дополнительное место для будущих элементов:

import sys

my_list = [1]
print(f"After append 1 element: {sys.getsizeof(my_list)} bytes")
# Output: 72 bytes

for i in range(10):
    my_list.append(i)
    print(f"After append {i+2} elements: {sys.getsizeof(my_list)} bytes")

# Output:
# After append 2 elements: 72 bytes
# After append 3 elements: 72 bytes
# After append 4 elements: 72 bytes
# After append 5 elements: 104 bytes  # Перераспределение
# After append 6 elements: 104 bytes
# ...
# After append 11 elements: 136 bytes

# Размер растёт скачками, а не линейно
# Это для оптимизации append() операций

Производительность

1. Создание

import timeit

# Создание списка
list_creation = timeit.timeit(
    lambda: [1, 2, 3, 4, 5],
    number=1_000_000
)
print(f"List creation: {list_creation:.4f} sec")
# Output: List creation: 0.1234 sec

# Создание кортежа
tuple_creation = timeit.timeit(
    lambda: (1, 2, 3, 4, 5),
    number=1_000_000
)
print(f"Tuple creation: {tuple_creation:.4f} sec")
# Output: Tuple creation: 0.0567 sec

print(f"Кортеж в {list_creation/tuple_creation:.1f}x быстрее")
# Output: Кортеж в 2.2x быстрее

2. Доступ к элементам

import timeit

my_list = list(range(1000))
my_tuple = tuple(range(1000))

# Доступ к элементу списка
list_access = timeit.timeit(
    lambda: my_list[500],
    number=10_000_000
)
print(f"List access: {list_access:.4f} sec")

# Доступ к элементу кортежа
tuple_access = timeit.timeit(
    lambda: my_tuple[500],
    number=10_000_000
)
print(f"Tuple access: {tuple_access:.4f} sec")

# Примерно одинаково (O(1) в обоих случаях)

3. Итерация

import timeit

my_list = list(range(10000))
my_tuple = tuple(range(10000))

# Итерация по списку
list_iteration = timeit.timeit(
    lambda: sum(my_list),
    number=100_000
)
print(f"List iteration: {list_iteration:.4f} sec")

# Итерация по кортежу
tuple_iteration = timeit.timeit(
    lambda: sum(my_tuple),
    number=100_000
)
print(f"Tuple iteration: {tuple_iteration:.4f} sec")

# Кортеж немного быстрее (~5-10%)

Основные различия

# 1. Изменяемость
my_list = [1, 2, 3]
my_list[0] = 99  # OK

my_tuple = (1, 2, 3)
my_tuple[0] = 99  # TypeError: 'tuple' object does not support item assignment

# 2. Методы
my_list.append(4)  # OK
my_list.extend([5, 6])  # OK
my_list.pop()  # OK

my_tuple.append(4)  # AttributeError

# 3. Как ключи словаря
d = {(1, 2): "tuple key"}  # OK (кортежи hashable)
me_list = {[1, 2]: "list key"}  # TypeError: unhashable type: 'list'

# 4. Распаковка
a, b, c = [1, 2, 3]  # OK
a, b, c = (1, 2, 3)  # OK

# 5. Строковое представление
str([1, 2, 3])  # '[1, 2, 3]'
str((1, 2, 3))  # '(1, 2, 3)'

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

Используй кортеж когда:

# 1. Нужна неизменяемость
config = {
    "database_host": "localhost",
    "database_port": 5432,
}
# Гарантируешь, что конфиг не изменится

if database_port == (1024, 65535):  # Проверка диапазона
    pass

# 2. Используешь как ключ словаря
coordinates = {
    (0, 0): "origin",
    (1, 1): "point",
    (10, 20): "another_point",
}

# 3. Хочешь гарантировать неизменяемость функции
def process_data(items: tuple) -> tuple:
    """Функция гарантирует, что не изменит input"""
    # items[0] = 99  # Это вызовет ошибку
    return tuple(x * 2 for x in items)

# 4. Множественный возврат из функции
def get_user():
    return ("John", "Doe", 30)  # Более идиоматично
    # vs return ["John", "Doe", 30]

# 5. Кэширование (кортежи hashable)
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    """Кэш работает с hashable аргументами"""
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 6. Named tuples
from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(11, 22)
print(p.x, p.y)  # 11 22

Когда использовать список

Используй список когда:

# 1. Нужно часто модифицировать
queue = []
queue.append("task1")  # Быстро добавить
queue.append("task2")
queue.pop(0)  # Получить первый элемент

# 2. Динамический размер
results = []
for user in users:
    results.append(process_user(user))  # Постепенно собираем результаты

# 3. Нужны методы list
data = [3, 1, 4, 1, 5, 9]
data.sort()  # Сортировка в месте
data.reverse()  # Разворот
data.remove(1)  # Удаление элемента
data.clear()  # Очистка

# 4. Когда данные изменяются
config_options = ["option1", "option2"]
config_options.append("option3")  # Добавляем новую опцию

Практический совет

# Оптимизация памяти в production
import sys

# Проблема: много кортежей в памяти
vectors = [
    (1.0, 2.0, 3.0),
    (4.0, 5.0, 6.0),
    # ... миллионы таких
]

# Решение 1: использовать numpy array
import numpy as np
vectors = np.array([
    [1.0, 2.0, 3.0],
    [4.0, 5.0, 6.0],
])

# Решение 2: использовать array.array
from array import array
vectors = [
    array('d', [1.0, 2.0, 3.0]),  # 'd' = double
    array('d', [4.0, 5.0, 6.0]),
]

# Решение 3: кортежи с кэшированием
from functools import lru_cache

@lru_cache(maxsize=10000)
def get_vector(x, y, z):
    return (x, y, z)

# Одно и то же значение в памяти только один раз
v1 = get_vector(1.0, 2.0, 3.0)
v2 = get_vector(1.0, 2.0, 3.0)
assert v1 is v2  # Одна и та же объект в памяти!

Итоговое сравнение

ХарактеристикаСписокКортеж
ПамятьБольше (с резервом)Меньше (ровно столько)
Скорость созданияМедленнееБыстрее (~2x)
Скорость доступаОдинаково O(1)Одинаково O(1)
ИтерацияМедленнееБыстрее (~5-10%)
ИзменяемостьДаНет
ХешируемостьНетДа (если элементы hashable)
МетодыМногоНет
Ключ в dictНетДа

Вывод: Кортеж действительно меньше весит и быстрее, но это не главное. Главное — выбирать в зависимости от задачи: кортежи для immutable данных и keys, списки для динамических коллекций. Обычно это вопрос semantic, не optimization.