← Назад к вопросам
Что меньше весит список или кортеж?
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.