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

Как понять, что объект изменяемый?

1.0 Junior🔥 211 комментариев
#Python Core

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

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

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

Как понять, что объект изменяемый

Это базовый, но ОЧЕНЬ важный навык. Неправильное понимание — источник багов. За 10+ лет я видел столько проблем из-за этого.

1. Базовое разделение

Изменяемые (Mutable):

list, dict, set, bytearray, custom objects

Неизменяемые (Immutable):

int, float, str, tuple, frozenset, bytes, bool, None

Но это не полный ответ! Нужно ПОНИМАТЬ, почему так работает.

2. Тест: попытка изменить объект

# Изменяемый: можно изменить содержимое
my_list = [1, 2, 3]
my_list[0] = 999  # ✅ РАБОТАЕТ
print(my_list)     # [999, 2, 3]

# Неизменяемый: нельзя изменить содержимое
my_tuple = (1, 2, 3)
my_tuple[0] = 999  # ❌ TypeError: tuple does not support item assignment

# Неизменяемый: нельзя переприсвоить элемент
my_string = "hello"
my_string[0] = 'H'  # ❌ TypeError: str does not support item assignment

3. Методы, которые работают только с мutable объектами

# Методы для list (изменяемый)
my_list = [1, 2, 3]
my_list.append(4)      # ✅ Работает
my_list.pop()          # ✅ Работает
my_list.sort()         # ✅ Работает
my_list[0] = 999       # ✅ Работает

# Методы для tuple (неизменяемый)
my_tuple = (1, 2, 3)
my_tuple.append(4)     # ❌ AttributeError: tuple has no attribute 'append'
my_tuple[0] = 999      # ❌ TypeError

# Методы для str (неизменяемый)
my_str = "hello"
my_str[0] = 'H'        # ❌ TypeError
# Но можно создать новый объект:
my_str = my_str.replace('h', 'H')  # ✅ Возвращает новую строку

4. Ключевой признак: id() меняется?

import copy

# Изменяемый объект: id остаётся, содержимое меняется
my_list = [1, 2, 3]
original_id = id(my_list)
my_list.append(4)  # Изменяем
print(f"ID до: {original_id}")
print(f"ID после: {id(my_list)}")
print(f"Одно и то же? {original_id == id(my_list)}")  # True

# Неизменяемый объект: нельзя изменить, только создать новый
my_tuple = (1, 2, 3)
original_id = id(my_tuple)
my_tuple = my_tuple + (4,)  # Создаёт НОВЫЙ tuple
print(f"ID до: {original_id}")
print(f"ID после: {id(my_tuple)}")
print(f"Одно и то же? {original_id == id(my_tuple)}")  # False

Правило: Если id() остаётся после "изменения" → объект изменяемый.

5. Проверка: создание новых объектов

# Строка (неизменяемая): каждая операция создаёт новую строку
name = "John"
name = name.upper()  # Новый объект!
print(id(name))      # Другой ID

# Список (изменяемый): методы часто меняют на месте
data = [1, 2, 3]
data.reverse()  # Меняет на месте, не возвращает новый список
print(data)     # [3, 2, 1]

# Но некоторые методы создают новый объект:
new_data = sorted(data)  # Возвращает новый список
print(data)      # [3, 2, 1] — не изменился
print(new_data)  # [1, 2, 3] — новый

6. Практические признаки мutable vs immutable

# ❌ Неправильно: передача как ключ dict (нужно immutable)
my_dict = {
    [1, 2]: "value"  # ❌ TypeError: unhashable type: 'list'
}

# ✅ Правильно: tuple как ключ
my_dict = {
    (1, 2): "value"  # ✅ Работает, tuple immutable
}

# ❌ Неправильно: mutable default argument
def bad_func(items=[]):  # Опасно! Список переиспользуется
    items.append(1)
    return items

# ✅ Правильно: None (immutable) как умолчание
def good_func(items=None):  # None immutable
    if items is None:
        items = []
    items.append(1)
    return items

7. Вложенные структуры: неполная иммутабельность

# Tuple сам по себе immutable, но содержимое может быть mutable!
data = ([1, 2], [3, 4])  # tuple содержит списки

data[0][0] = 999  # ✅ РАБОТАЕТ! Меняем содержимое вложенного списка
print(data)        # ([999, 2], [3, 4])

data[0] = [999]    # ❌ TypeError: tuple does not support item assignment

# Вывод: tuple immutable в отношении СВОЕЙ структуры,
# но не в отношении СОДЕРЖИМОГО вложенных mutable объектов

8. Использование hash()

Изменяемые объекты обычно не имеют стабильного hash:

# Неизменяемые объекты: имеют hash
print(hash(42))                  # ✅
print(hash("hello"))            # ✅
print(hash((1, 2, 3)))          # ✅

# Изменяемые объекты: НЕ имеют hash
print(hash([1, 2, 3]))          # ❌ TypeError: unhashable type: 'list'
print(hash({"a": 1}))          # ❌ TypeError: unhashable type: 'dict'

# Поэтому изменяемые объекты нельзя использовать как ключи dict или элементы set
my_set = {(1, 2), (3, 4)}  # ✅ Tuple — OK
my_set = {[1, 2], [3, 4]}  # ❌ List — TypeError

9. Встроенная функция isinstance()

# Проверка типа (работает, но не идеально)
def is_mutable(obj):
    """Приблизительная проверка"""
    mutable_types = (list, dict, set, bytearray)
    return isinstance(obj, mutable_types)

print(is_mutable([1, 2]))        # True
print(is_mutable((1, 2)))        # False
print(is_mutable("hello"))       # False
print(is_mutable({"a": 1}))      # True

# Но это не 100% правда для custom объектов
class MyClass:
    pass

print(is_mutable(MyClass()))     # False (но объект мутабелен!)

10. Custom объекты — всегда изменяемы (по умолчанию)

class User:
    def __init__(self, name):
        self.name = name

user = User("John")
user.name = "Jane"  # ✅ Можно изменить

print(f"ID до: {id(user)}")
user.name = "Bob"
print(f"ID после: {id(user)}")
print(f"Одно и то же? {id(user) == id(user)}")  # True

# Custom объекты изменяемы по умолчанию
# Если нужен immutable объект — используй @dataclass с frozen=True
from dataclasses import dataclass

@dataclass(frozen=True)
class ImmutableUser:
    name: str

user2 = ImmutableUser("John")
user2.name = "Jane"  # ❌ FrozenInstanceError

11. Проверка: есть ли метод hash?

# Immutable объекты обычно hashable
print(hasattr(str, '__hash__'))      # True
print(hasattr(tuple, '__hash__'))    # True
print(hasattr(int, '__hash__'))      # True

# Mutable объекты обычно нет
print(hasattr(list, '__hash__'))     # False
print(hasattr(dict, '__hash__'))     # False
print(hasattr(set, '__hash__'))      # False

# Как проверить конкретный объект
print(hash.__doc__)  # Что такое hash
print(hash([1, 2]))  # TypeError

12. Практический чеклист

# Как проверить, изменяемый ли объект?

test_obj = [1, 2, 3]

# Способ 1: попытка изменить и поймать ошибку
try:
    test_obj[0] = 999
    print("Изменяемый")
except TypeError:
    print("Неизменяемый")

# Способ 2: проверить наличие методов изменения
print(hasattr(test_obj, 'append'))   # list → True
print(hasattr(test_obj, 'pop'))      # list → True
print(hasattr(test_obj, 'sort'))     # list → True

test_obj2 = (1, 2, 3)
print(hasattr(test_obj2, 'append'))  # tuple → False

# Способ 3: проверить hash
try:
    hash(test_obj)
    print("Неизменяемый (hashable)")
except TypeError:
    print("Изменяемый (unhashable)")

Таблица: Полный справочник

ТипMutableМетодыМожно ключ dictHash
listappend, pop, sort
dictupdate, pop, clear
setadd, remove, pop
bytearrayappend, pop
tupleтолько viewing
strreplace, split (новый)
int-
frozenset-
bytes-

Золотое правило

Изменяемые объекты можно менять на месте, неизменяемые нет. Проверяй: "Могу ли я вызвать метод, который меняет это?" Если да — изменяемый. Используй эти знания для правильного проектирования: неизменяемые как ключи dict, изменяемые только для локального мутирования.

Как понять, что объект изменяемый? | PrepBro