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

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

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

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

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

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

Определение неизменяемых объектов в Python

Это важная концепция для понимания производительности, безопасности и предсказуемости кода. За 10+ лет я выработал методы быстро определять, может ли объект изменяться или нет.

Встроенные типы: изменяемые vs неизменяемые

# НЕИЗМЕНЯЕМЫЕ (Immutable)
int_obj = 42              # int
float_obj = 3.14          # float
str_obj = "hello"         # str
bool_obj = True           # bool
none_obj = None           # NoneType
complex_obj = 3 + 4j      # complex
frozenset_obj = frozenset([1, 2, 3])  # frozenset
bytes_obj = b"data"       # bytes
range_obj = range(10)     # range
tuple_obj = (1, 2, 3)     # tuple (если содержит immutable)

# ИЗМЕНЯЕМЫЕ (Mutable)
list_obj = [1, 2, 3]      # list
dict_obj = {"a": 1}       # dict
set_obj = {1, 2, 3}       # set
bytearray_obj = bytearray(b"data")  # bytearray

Способ 1: Проверка через хеш (hash)

Неизменяемые объекты можно хешировать:

# Неизменяемый объект — можно хешировать
try:
    hash_value = hash((1, 2, 3))  # tuple
    print(f"Хеш tuple: {hash_value}")  # Работает
except TypeError:
    print("Объект неизменяемый")

# Попытка хешировать список (изменяемый)
try:
    hash_value = hash([1, 2, 3])  # list
    print(f"Хеш list: {hash_value}")
except TypeError as e:
    print(f"Список не hashable: {e}")
    # TypeError: unhashable type: 'list'

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

# Попытаемся изменить tuple
tuple_obj = (1, 2, 3)
try:
    tuple_obj[0] = 99  # Попытаемся изменить
except TypeError as e:
    print(f"Tuple неизменяемый: {e}")
    # TypeError: 'tuple' object does not support item assignment

# Список можно изменить
list_obj = [1, 2, 3]
list_obj[0] = 99  # Работает!
print(list_obj)  # [99, 2, 3]

Способ 3: Проверка через id() при переприсваивании

# Неизменяемый объект — переприсваивание создаёт новый объект
str1 = "hello"
id_before = id(str1)

str2 = str1 + " world"  # Новый объект
id_after = id(str2)

print(id_before == id_after)  # False — разные объекты

# Изменяемый объект — изменение оставляет же объект
list1 = [1, 2, 3]
id_before = id(list1)

list1.append(4)  # Изменяем существующий объект
id_after = id(list1)

print(id_before == id_after)  # True — ТОТ ЖЕ объект

Способ 4: Использование inspect модуля

import inspect
from collections.abc import Hashable

def is_immutable(obj) -> bool:
    """Проверяет, неизменяемый ли объект"""
    
    # Способ 1: проверяем, можно ли хешировать
    try:
        hash(obj)
        return True
    except TypeError:
        return False

def is_mutable(obj) -> bool:
    """Проверяет, изменяемый ли объект"""
    return not is_immutable(obj)

# Тестирование
print(is_immutable(42))        # True
print(is_immutable("hello"))   # True
print(is_immutable((1, 2, 3))) # True
print(is_immutable([1, 2, 3])) # False
print(is_immutable({1, 2, 3})) # False
print(is_immutable(frozenset([1, 2])))  # True

Способ 5: Проверка через типы

from typing import get_origin, get_args

# Встроенные неизменяемые типы
IMMUTABLE_TYPES = (
    int, float, str, bool, bytes, type(None),
    complex, frozenset, tuple, range
)

# Встроенные изменяемые типы
MUTABLE_TYPES = (
    list, dict, set, bytearray
)

def quick_check_immutable(obj) -> bool:
    """Быстрая проверка по типу"""
    return type(obj) in IMMUTABLE_TYPES

def quick_check_mutable(obj) -> bool:
    """Быстрая проверка по типу"""
    return type(obj) in MUTABLE_TYPES

print(quick_check_immutable("hello"))   # True
print(quick_check_mutable([1, 2, 3]))   # True

Способ 6: Кастомные неизменяемые классы

from dataclasses import dataclass

# Способ 1: использовать @dataclass с frozen=True
@dataclass(frozen=True)
class ImmutablePoint:
    """Неизменяемая точка"""
    x: float
    y: float

point = ImmutablePoint(1.0, 2.0)
print(hash(point))  # Работает! Можно использовать как ключ

# Попытка изменить
try:
    point.x = 3.0
except AttributeError as e:
    print(f"Нельзя изменить: {e}")
    # AttributeError: can't set attribute

# Способ 2: использовать __slots__ и __setattr__
class ImmutableUser:
    """Неизменяемый пользователь"""
    __slots__ = ("name", "email")
    
    def __init__(self, name: str, email: str):
        object.__setattr__(self, "name", name)
        object.__setattr__(self, "email", email)
    
    def __setattr__(self, name, value):
        raise AttributeError("Object is immutable")
    
    def __hash__(self):
        return hash((self.name, self.email))
    
    def __eq__(self, other):
        if not isinstance(other, ImmutableUser):
            return False
        return self.name == other.name and self.email == other.email

user = ImmutableUser("John", "john@example.com")
print(hash(user))  # Работает!

# Попытка изменить
try:
    user.name = "Jane"
except AttributeError as e:
    print(f"Нельзя изменить: {e}")

Сложный случай: tuple с изменяемыми элементами

# Tuple сам по себе неизменяемый, но может содержать изменяемые объекты
mixed_tuple = (1, 2, [3, 4])  # tuple содержит list

# Нельзя изменить сам tuple
try:
    mixed_tuple[0] = 99  # TypeError
except TypeError:
    print("Нельзя изменить элементы tuple")

# Но можно изменить список ВНУТРИ tuple
mixed_tuple[2].append(5)  # Работает!
print(mixed_tuple)  # (1, 2, [3, 4, 5]) — список изменился!

# Попытка хешировать
try:
    hash(mixed_tuple)  # TypeError!
except TypeError:
    print("Tuple с изменяемыми элементами не hashable")

# Вывод: даже если контейнер неизменяемый, если он содержит
# изменяемые объекты, то он не hashable!

Практические примеры: когда это важно

# Использование как ключ в dict — только неизменяемые!
data = {}

# OK — tuple неизменяемый
data[(1, 2, 3)] = "point"
print(data)  # {(1, 2, 3): 'point'}

# Ошибка — список изменяемый
try:
    data[[1, 2, 3]] = "point"
except TypeError as e:
    print(f"Ошибка: {e}")
    # TypeError: unhashable type: 'list'

# Использование в set — только неизменяемые!
unique_items = set()
unique_items.add(("x", "y"))  # OK
print(unique_items)  # {('x', 'y')}

try:
    unique_items.add(["x", "y"])  # Ошибка
except TypeError:
    print("Списки нельзя добавлять в set")

Быстрый чеклист

# Вопрос: объект неизменяемый?
# Ответ: можно ли его хешировать?

def check_immutable_quick(obj) -> bool:
    try:
        hash(obj)
        return True  # Неизменяемый
    except TypeError:
        return False  # Изменяемый

# Вывод: если hash() работает — объект неизменяемый!

Почему это важно?

  1. Производительность: неизменяемые объекты можно оптимизировать
  2. Безопасность: неизменяемые объекты safe для многопоточности
  3. Функциональное программирование: гарантирует отсутствие побочных эффектов
  4. Кэширование: неизменяемые объекты можно кэшировать
  5. Ключи в dict/set: только неизменяемые объекты

Просто запомни: если object hashable (можно вызвать hash()), то он неизменяемый. Это универсальный способ проверить в любой ситуации.

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