← Назад к вопросам
Что такое неизменяемый объект в Python?
2.2 Middle🔥 131 комментариев
#Python Core#Soft Skills
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Неизменяемые объекты в Python (Immutable Objects)
Неизменяемый объект (immutable object) — это объект, который нельзя изменить после создания. Если попытаться изменить такой объект, Python создаст новый объект с изменёнными данными. Это фундаментальное понятие для понимания работы памяти и производительности в Python.
Какие объекты неизменяемые?
# ✓ НЕИЗМЕНЯЕМЫЕ (Immutable):
int # 5, -10, 1000000
float # 3.14, 2.0
str # "hello", "Python"
tuple # (1, 2, 3), ("a", "b")
bool # True, False
frozenset # frozenset({1, 2, 3})
bytes # b"data"
None # None
# ❌ ИЗМЕНЯЕМЫЕ (Mutable):
list # [1, 2, 3]
dict # {"key": "value"}
set # {1, 2, 3}
bytearray # bytearray(b"data")
объекты классов # user = User()
Демонстрация: строки
# Строки НЕИЗМЕНЯЕМЫ
s = "hello"
print(id(s)) # 140234567890000
# Попытка "изменить"
s = s + " world" # На самом деле создаёт НОВУЮ строку
print(id(s)) # 140234567890100 — ДРУГОЙ id!
# Старая строка "hello" всё ещё в памяти
# Сборка мусора удалит её позже
# Это то же самое что:
s = "hello"
old_id = id(s)
s = "hello world" # Новый объект
new_id = id(s)
print(old_id == new_id) # False
Попытка модифицировать
# Если бы строки были изменяемы (чего нет):
s = "hello"
s[0] = "H" # ❌ TypeError: 'str' object does not support item assignment
# Правильный способ:
s = "hello"
s = s[0].upper() + s[1:] # Создаёт новую строку
print(s) # "Hello"
# Или используем метод (который тоже создаёт новую):
s = "hello"
s = s.replace("h", "H") # Новая строка
print(s) # "Hello"
Кортежи (tuples)
# Кортежи неизменяемы
t = (1, 2, 3)
print(id(t)) # 140234567890000
# Попытка изменить элемент:
t[0] = 5 # ❌ TypeError: 'tuple' object does not support item assignment
# Но если элементы изменяемы:
t = ([1, 2], [3, 4])
t[0][0] = 999 # ✓ Работает! Список внутри кортежа изменился
print(t) # ([999, 2], [3, 4])
# Сам кортеж остался неизменяемым (тот же id)
# Но элемент (список) изменился
Пример: список vs кортеж
# СПИСОК (изменяемый)
list_obj = [1, 2, 3]
original_id = id(list_obj)
list_obj.append(4) # Изменяем на месте
print(id(list_obj) == original_id) # True (ТОТ ЖЕ объект)
print(list_obj) # [1, 2, 3, 4]
# КОРТЕЖ (неизменяемый)
tuple_obj = (1, 2, 3)
original_id = id(tuple_obj)
new_tuple = tuple_obj + (4,) # Создаёт НОВЫЙ кортеж
print(id(new_tuple) == original_id) # False (другой объект)
print(tuple_obj) # (1, 2, 3) — не изменился
print(new_tuple) # (1, 2, 3, 4)
Словари из фильма о производительности
# Словарь изменяемый
user = {"name": "Alice", "age": 30}
original_id = id(user)
# Можем менять значения
user["age"] = 31 # Тот же объект
print(id(user) == original_id) # True
# Но если создаём новый словарь
user = {"name": "Alice", "age": 31}
print(id(user) == original_id) # False — новый объект
Копирование и неизменяемость
# Неизменяемые объекты
a = "hello"
b = a # Просто ссылка на тот же объект
print(id(a) == id(b)) # True
# Если переприсвоить:
a = "world"
print(id(a) == id(b)) # False — новый объект
print(b) # "hello" — не изменилась
# Изменяемые объекты
list_a = [1, 2, 3]
list_b = list_a # Ссылка на тот же объект
print(id(list_a) == id(list_b)) # True
list_a.append(4) # Изменяем
print(id(list_a) == id(list_b)) # Всё ещё True!
print(list_b) # [1, 2, 3, 4] — изменилась!
# Правильное копирование:
list_b = list_a.copy() # Или list(list_a)
list_a.append(5)
print(list_a) # [1, 2, 3, 4, 5]
print(list_b) # [1, 2, 3, 4] — не изменилась
Практический пример: функции
# Неизменяемые аргументы безопасны
def process_string(s: str) -> str:
"""Строка не может быть изменена внутри функции."""
s = s + " processed" # Создаёт новую строку
return s
original = "hello"
result = process_string(original)
print(original) # "hello" — не изменилась
print(result) # "hello processed"
# Изменяемые аргументы НЕ безопасны
def add_item(items: list, item: str):
"""Список МОЖЕТ быть изменён!"""
items.append(item) # Изменяет оригинальный список
return items
my_list = ["a", "b"]
result = add_item(my_list, "c")
print(my_list) # ["a", "b", "c"] — ИЗМЕНИЛСЯ!
print(result) # ["a", "b", "c"]
# Правильно: не менять входной параметр
def add_item_safe(items: list, item: str) -> list:
"""Создаёт новый список."""
return items + [item]
my_list = ["a", "b"]
result = add_item_safe(my_list, "c")
print(my_list) # ["a", "b"] — не изменился
print(result) # ["a", "b", "c"]
Хеширование и словари
# Неизменяемые объекты хешируемы (hashable)
# Их можно использовать как ключи словарей
# ✓ Строка как ключ
user = {"name": "Alice", "age": 30} # Строки это ключи
# ✓ Кортеж как ключ
coordinates = {(0, 0): "origin", (1, 2): "point A"}
# ✓ Целое число как ключ
ranking = {1: "Gold", 2: "Silver", 3: "Bronze"}
# ❌ Список как ключ — ошибка!
user = {["Alice"]: 30} # TypeError: unhashable type: 'list'
# ❌ Словарь как ключ — ошибка!
user = {{"name": "Alice"}: 30} # TypeError: unhashable type: 'dict'
# Почему? Потому что изменяемые объекты могут меняться
# После добавления в словарь ключ изменится
# → словарь развалится
# Решение: используй frozenset вместо set
frozenset_key = frozenset([1, 2, 3])
data = {frozenset_key: "immutable set"} # ✓ Работает
Performance: строк неизменяемы
# НЕПРАВИЛЬНО: конкатенация строк в цикле
result = ""
for i in range(1000000):
result += str(i) # ❌ Создаёт 1M новых строк!
# Очень медленно!
# ПРАВИЛЬНО: используй список
result = []
for i in range(1000000):
result.append(str(i)) # Добавляет в список
final = "".join(result) # Один раз создаёт большую строку
# ПРАВИЛЬНО: используй f-strings или format
data = []
for i in range(100):
data.append(f"Item {i}") # Оптимизированный способ
Default mutable arguments (ЛОВУШКА!)
# ❌ ОШИБКА: изменяемый аргумент по умолчанию
def add_item(item, items=[]): # ← items это общий объект!
items.append(item)
return items
result1 = add_item("a") # ["a"]
result2 = add_item("b") # ["a", "b"] — НЕ ["b"]!
print(result1) # ["a", "b"] — изменился!
# Список создаётся один раз при определении функции
# И переиспользуется для всех вызовов
# ✓ ПРАВИЛЬНО: None по умолчанию
def add_item_safe(item, items=None):
if items is None:
items = []
items.append(item)
return items
result1 = add_item_safe("a") # ["a"]
result2 = add_item_safe("b") # ["b"]
# Или ещё лучше: не менять входные параметры
def add_item_better(item, items=None):
if items is None:
items = []
return items + [item] # Новый список
Создание неизменяемых классов
from dataclasses import dataclass
from typing import Tuple
# СПОСОБ 1: Использовать frozen dataclass
@dataclass(frozen=True) # ← frozen=True делает неизменяемым
class Point:
x: float
y: float
p = Point(1.0, 2.0)
print(hash(p)) # Можем хешировать
# p.x = 3.0 # ❌ FrozenInstanceError
# СПОСОБ 2: Использовать NamedTuple
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
p = Point(1.0, 2.0)
print(hash(p)) # Хешируемый
# p.x = 3.0 # ❌ AttributeError
# СПОСОБ 3: Переопределить __setattr__
class ImmutablePoint:
def __init__(self, x: float, y: float):
object.__setattr__(self, 'x', x)
object.__setattr__(self, 'y', y)
def __setattr__(self, name, value):
raise TypeError(f"Cannot modify {name}")
def __hash__(self):
return hash((self.x, self.y))
p = ImmutablePoint(1.0, 2.0)
print(hash(p)) # ✓ Работает
# p.x = 3.0 # ❌ TypeError
Использование как ключи
# Неизменяемые объекты можно использовать как ключи
users_by_name = {
"Alice": User(name="Alice", age=30),
"Bob": User(name="Bob", age=25),
}
# Кортежи как ключи для многомерных данных
temperature_data = {
("New York", "2024-01-01"): 2.5,
("New York", "2024-01-02"): 3.1,
("Los Angeles", "2024-01-01"): 18.3,
}
temp_ny = temperature_data[("New York", "2024-01-01")]
print(temp_ny) # 2.5
# frozenset для наборов как ключей
friends_of = {
frozenset(["Alice", "Bob"]): "best friends",
frozenset(["Charlie", "Diana"]): "colleagues",
}
Ключевые моменты
- Неизменяемые объекты (int, str, tuple) нельзя менять
- Попытка изменить создаёт новый объект с новым id
- Изменяемые объекты (list, dict, set) можно менять на месте
- Копирование критично для изменяемых объектов
- Неизменяемые хешируемы → можно использовать как ключи
- Default mutable arguments это частая ошибка
- Performance конкатенация строк медленная → используй join()
- Frozen dataclass способ создать неизменяемые объекты