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

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

1.2 Junior🔥 251 комментариев
#Python Core

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

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

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

Проверка изменяемости типов данных в Python

В Python есть встроенные типы, которые являются mutable (изменяемые) и immutable (неизменяемые). Это критически влияет на поведение при присваивании и передаче аргументов функциям.

Встроенные типы

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

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

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

  • list, dict, set
  • bytearray

Способ 1: Используй id() и проверь, изменяется ли адрес в памяти

За неизменяемыми типами стоит идея: переменная может быть переассвоена, но сам объект не меняется.

# Неизменяемый тип (int)
x = 10
id_before = id(x)
x = x + 5  # Новый объект!
id_after = id(x)

print(f"ID изменился: {id_before != id_after}")  # True
print(f"Старое значение: 10, новое значение: 15")

# Неизменяемый тип (tuple)
t = (1, 2, 3)
id_before = id(t)
t = t + (4,)  # Новый объект!
id_after = id(t)

print(f"ID изменился: {id_before != id_after}")  # True

Это неудобная проверка. Лучше способы ниже.

Способ 2: Попытайся изменить объект (быстрый способ)

Самый практичный способ — просто попробовать изменить и посмотреть, получится ли:

def is_mutable(obj):
    """Проверить, является ли объект mutable"""
    if isinstance(obj, (list, dict, set, bytearray)):
        return True
    elif isinstance(obj, (str, tuple, frozenset, bytes, int, float, bool, type(None))):
        return False
    else:
        return None  # Неизвестный тип

print(is_mutable([1, 2, 3]))        # True (list)
print(is_mutable((1, 2, 3)))         # False (tuple)
print(is_mutable({"a": 1}))          # True (dict)
print(is_mutable("hello"))           # False (str)
print(is_mutable(42))                # False (int)

Способ 3: Проверь наличие методов setitem или delitem

Мутабельные типы имеют методы для изменения (как правило):

def is_mutable_by_methods(obj):
    """Проверить по наличию методов изменения"""
    return hasattr(obj, '__setitem__') or hasattr(obj, '__delitem__')

print(is_mutable_by_methods([1, 2, 3]))      # True
print(is_mutable_by_methods({1, 2, 3}))       # True (set)
print(is_mutable_by_methods((1, 2, 3)))       # False
print(is_mutable_by_methods("hello"))         # False

# Внимание! Строки имеют __getitem__, но не __setitem__
print(hasattr("hello", '__getitem__'))       # True (можно индексировать)
print(hasattr("hello", '__setitem__'))       # False (нельзя менять)

Способ 4: Проверь наличие hash() (для immutable)

Неизменяемые объекты можно использовать как ключи в dict и элементы в set. Это требует hashability:

def is_hashable(obj):
    """Если объект hashable — он скорее всего immutable"""
    try:
        hash(obj)
        return True
    except TypeError:
        return False

print(is_hashable([1, 2, 3]))        # False (list)
print(is_hashable((1, 2, 3)))         # True (tuple)
print(is_hashable({"a": 1}))          # False (dict)
print(is_hashable(frozenset([1, 2]))) # True (frozenset)
print(is_hashable(42))                # True (int)

Практическое применение:

# Использовать как ключ в dict
my_dict = {
    (1, 2): "value1",      # Tuple работает (hashable)
    frozenset([1, 2]): "value2",  # Frozenset работает
    # [1, 2]: "value3",    # List НЕ работает! TypeError
    # {1, 2}: "value4",    # Set НЕ работает! TypeError
}

Способ 5: Используй collections.abc модуль (правильный способ)

Для более сложных типов используй ABCs (Abstract Base Classes):

from collections.abc import Hashable, Sequence, MutableSequence, Mapping, MutableMapping, Set, MutableSet

def analyze_type(obj):
    """Анализ типа объекта"""
    print(f"Тип: {type(obj).__name__}")
    print(f"Hashable: {isinstance(obj, Hashable)}")
    print(f"Sequence: {isinstance(obj, Sequence)}")
    print(f"MutableSequence: {isinstance(obj, MutableSequence)}")
    print(f"Mapping: {isinstance(obj, Mapping)}")
    print(f"MutableMapping: {isinstance(obj, MutableMapping)}")
    print(f"Set: {isinstance(obj, Set)}")
    print(f"MutableSet: {isinstance(obj, MutableSet)}")
    print()

analyze_type([1, 2, 3])    # MutableSequence
analyze_type((1, 2, 3))     # Sequence (не Mutable)
analyze_type({1, 2, 3})     # MutableSet
analyze_type(frozenset([1, 2])) # Set (не Mutable)
analyze_type({"a": 1})      # MutableMapping
analyze_type(("a", 1))      # Не Mapping

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

1. Мутабельные значения по умолчанию (опасно!):

# ОШИБКА: использование mutable объекта как default
def add_to_list(item, my_list=[]):
    my_list.append(item)
    return my_list

print(add_to_list(1))       # [1]
print(add_to_list(2))       # [1, 2] (не [2]!)
print(add_to_list(3))       # [1, 2, 3]
# Потому что пустой список создаётся один раз при определении функции!

# ПРАВИЛЬНО: использование immutable или None
def add_to_list_fixed(item, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(item)
    return my_list

print(add_to_list_fixed(1))  # [1]
print(add_to_list_fixed(2))  # [2]
print(add_to_list_fixed(3))  # [3]

2. Копирование vs присваивание:

# Неизменяемый тип
a = 10
b = a
b += 5
print(f"a: {a}, b: {b}")  # a: 10, b: 15 (a не изменилась)

# Изменяемый тип
list_a = [1, 2, 3]
list_b = list_a  # Это всё ещё указатель на тот же список!
list_b.append(4)
print(f"list_a: {list_a}")  # [1, 2, 3, 4] (изменилась!)
print(f"list_b: {list_b}")  # [1, 2, 3, 4]

# Правильное копирование для mutable
list_a = [1, 2, 3]
list_b = list_a.copy()  # Неглубокая копия
list_b.append(4)
print(f"list_a: {list_a}")  # [1, 2, 3]
print(f"list_b: {list_b}")  # [1, 2, 3, 4]

# Или через list() конструктор
list_b = list(list_a)

# Для вложенных структур нужна глубокая копия
import copy
list_a = [[1, 2], [3, 4]]
list_b = copy.deepcopy(list_a)
list_b[0].append(5)
print(f"list_a: {list_a}")  # [[1, 2], [3, 4]]
print(f"list_b: {list_b}")  # [[1, 2, 5], [3, 4]]

3. Кастомные классы:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p1 = Person("Alice", 30)
p2 = p1
p2.age = 31
print(f"p1.age: {p1.age}")  # 31 (изменилась, потому что это один объект)

# Кастомный класс обычно mutable, если он не имеет __hash__ и __eq__
my_dict = {p1: "data"}  # TypeError! Объекты не hashable по умолчанию

# Чтобы сделать класс immutable
@dataclasses.dataclass(frozen=True)
class ImmutablePerson:
    name: str
    age: int

ip1 = ImmutablePerson("Bob", 25)
ip2 = ip1
# ip2.age = 26  # AttributeError: can't set attribute
my_dict = {ip1: "data"}  # Работает!

Сравнительная таблица

ТипMutableHashableПримечание
intПримитив
floatПримитив
strНеизменяема
bytesНеизменяема
listИспользуй tuple для ключей
tuple✅**Только если элементы hashable
dictИспользуй frozenset для ключей
setИспользуй frozenset для ключей
frozensetImmutable версия set
boolTrue/False

Лучшая практика

# 1. Помни: immutable безопаснее в многопоточности
# 2. Используй tuple вместо list для неизменяемых данных
# 3. Избегай mutable default arguments
# 4. Копируй mutable объекты, когда передаёшь их
# 5. Используй frozen dataclasses для immutable классов

from dataclasses import dataclass

@dataclass(frozen=True)
class Config:
    host: str
    port: int
    debug: bool = False

config = Config("localhost", 8000)
# config.debug = True  # AttributeError

# Можно использовать как ключ
cache = {config: "data"}

Это один из самых важных концептов для понимания Python. Различие между mutable и immutable влияет на то, как пишется безопасный, предсказуемый код.

Как проверить, изменяемый тип данных или нет? | PrepBro