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

Что такое изменяемость типов данных?

1.6 Junior🔥 161 комментариев
#Python Core

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

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

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

Что такое изменяемость типов данных (Mutability)

Изменяемость (mutability) — это свойство объекта в программировании, определяющее, может ли его состояние измениться после создания. В Python существуют изменяемые (mutable) и неизменяемые (immutable) типы данных, и это имеет критическое значение для написания надёжного кода.

Основные понятия

Изменяемый (mutable) тип — объект, который можно изменить после создания, без создания нового объекта.

Неизменяемый (immutable) тип — объект, который нельзя изменить после создания. При попытке изменения создаётся новый объект.

# НЕИЗМЕНЯЕМЫЙ ТИП: int
x = 10
y = x
x = 20
print(y)  # 10 (y не изменился, x ссылается на новый объект)
print(id(x), id(y))  # Разные id

# ИЗМЕНЯЕМЫЙ ТИП: list
list1 = [1, 2, 3]
list2 = list1
list1.append(4)
print(list2)  # [1, 2, 3, 4] (list2 тоже изменился!)
print(id(list1) == id(list2))  # True (один и тот же объект)

Изменяемые типы в Python

list (список)

my_list = [1, 2, 3]
my_list[0] = 100  # Изменяем элемент
my_list.append(4)  # Добавляем элемент
my_list.pop()      # Удаляем элемент

print(my_list)  # [100, 2, 3]

dict (словарь)

my_dict = {'a': 1, 'b': 2}
my_dict['a'] = 100  # Изменяем значение
my_dict['c'] = 3    # Добавляем ключ
del my_dict['b']    # Удаляем ключ

print(my_dict)  # {'a': 100, 'c': 3}

set (множество)

my_set = {1, 2, 3}
my_set.add(4)      # Добавляем элемент
my_set.remove(1)   # Удаляем элемент
my_set.update([5, 6])  # Добавляем несколько

print(my_set)  # {2, 3, 4, 5, 6}

bytearray (массив байтов)

my_bytes = bytearray(b'hello')
my_bytes[0] = ord('H')  # Изменяем первый байт
print(my_bytes)  # bytearray(b'Hello')

Собственные классы (по умолчанию)

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

person = Person('John', 30)
person.age = 31  # Изменяем атрибут
print(person.age)  # 31

Неизменяемые типы в Python

int, float, complex (числа)

x = 10
x = 20  # Создаётся новый объект, старый остаётся в памяти
print(id(10), id(20))  # Разные объекты

str (строка)

s = 'hello'
s = s.upper()  # Создаётся новая строка
print(s)  # 'HELLO'
# Исходная строка 'hello' остаётся в памяти (интернирована)

# Попытка изменить элемент вызовет ошибку:
# s[0] = 'H'  # TypeError: 'str' object does not support item assignment

tuple (кортеж)

t = (1, 2, 3)
print(t[0])  # Читаем элемент
# t[0] = 100  # TypeError: 'tuple' object does not support item assignment
# t.append(4)  # AttributeError: 'tuple' object has no attribute 'append'

frozenset (неизменяемое множество)

fs = frozenset([1, 2, 3])
# fs.add(4)  # AttributeError: 'frozenset' object has no attribute 'add'
print(fs)  # frozenset({1, 2, 3})

bytes (байты)

b = b'hello'
# b[0] = ord('H')  # TypeError: 'bytes' object does not support item assignment
print(b)  # b'hello'

Таблица типов

Тип          | Изменяемость | Применение
             |              |
—————————————|——————————————|————————————————————————————
int          | Immutable    | Числовые значения
float        | Immutable    | Дробные числа
str          | Immutable    | Текст
bytes        | Immutable    | Бинарные данные (read-only)
tuple        | Immutable    | Неизменяемые последовательности
frozenset    | Immutable    | Неизменяемые наборы
—————————————|——————————————|————————————————————————————
list         | Mutable      | Изменяемые последовательности
dict         | Mutable      | Ключ-значение
set          | Mutable      | Уникальные значения
bytearray    | Mutable      | Изменяемые бинарные данные
class        | Mutable      | Пользовательские объекты

Критические ошибки с изменяемостью

1. Опасность использования mutable в качестве параметра по умолчанию

# ❌ НЕПРАВИЛЬНО
def add_item(item, items=[]):
    items.append(item)
    return items

list1 = add_item(1)
print(list1)  # [1]
list2 = add_item(2)
print(list2)  # [1, 2] — НЕПРАВИЛЬНО! Ожидается [2]
print(list1 is list2)  # True — они один и тот же объект!

# Проблема: список создаётся один раз при определении функции
# и переиспользуется для всех вызовов

# ✅ ПРАВИЛЬНО
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

list1 = add_item(1)
print(list1)  # [1]
list2 = add_item(2)
print(list2)  # [2] ✓

2. Неожиданное изменение при присваивании

# ❌ НЕПРАВИЛЬНО
original = [1, 2, 3]
copy = original  # Не копирование, а присваивание ссылки!
original.append(4)
print(copy)  # [1, 2, 3, 4] — неожиданное изменение!

# ✅ ПРАВИЛЬНО
original = [1, 2, 3]
copy = original.copy()  # Поверхностное копирование
# или
copy = list(original)
# или
copy = original[:]  # Слайс

original.append(4)
print(copy)  # [1, 2, 3] ✓
print(original is copy)  # False

3. Глубокое копирование

import copy

# Поверхностное копирование (shallow copy)
original = [[1, 2], [3, 4]]
shallow = original.copy()
original[0][0] = 100
print(shallow)  # [[100, 2], [3, 4]] — вложенный список изменился!

# Глубокое копирование (deep copy)
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
original[0][0] = 100
print(deep)  # [[1, 2], [3, 4]] ✓ — вложенный список не изменился

4. Изменяемость как ключ словаря

# ❌ НЕПРАВИЛЬНО
my_dict = {[1, 2]: 'value'}  # TypeError: unhashable type: 'list'

# ✓ ПРАВИЛЬНО
my_dict = {(1, 2): 'value'}  # tuple — неизменяемый тип
print(my_dict[(1, 2)])  # 'value'

Практический пример: Класс с правильной изменяемостью

from typing import List
from copy import deepcopy

class ImmutableUser:
    """Неизменяемый класс пользователя"""
    def __init__(self, name: str, tags: List[str]):
        self._name = name
        self._tags = tuple(tags)  # Преобразуем в tuple
    
    @property
    def name(self) -> str:
        return self._name
    
    @property
    def tags(self) -> tuple:
        return self._tags
    
    def with_tag(self, tag: str) -> 'ImmutableUser':
        """Возвращает новый объект с добавленным тегом"""
        new_tags = list(self._tags) + [tag]
        return ImmutableUser(self._name, new_tags)
    
    def __hash__(self):
        return hash((self._name, self._tags))
    
    def __eq__(self, other):
        return self._name == other._name and self._tags == other._tags

user1 = ImmutableUser('John', ['python', 'java'])
user2 = user1.with_tag('golang')

print(user1.tags)  # ('python', 'java')
print(user2.tags)  # ('python', 'java', 'golang')
print(user1 is user2)  # False

Производительность

import time

# String concatenation (каждый раз новый объект)
start = time.time()
s = ""
for i in range(1000):
    s += str(i)  # Неэффективно!
print(f"String concat: {time.time() - start:.4f}s")

# Правильный способ
start = time.time()
parts = [str(i) for i in range(1000)]
s = ''.join(parts)  # Эффективно
print(f"Join: {time.time() - start:.4f}s")
# Join обычно в 100+ раз быстрее

Заключение

Понимание изменяемости типов критично для:

  1. Написания корректного кода — избежание неожиданных изменений
  2. Производительности — выбор правильных структур данных
  3. Thread-безопасности — неизменяемые объекты безопаснее в многопоточных приложениях
  4. Дизайна API — использование неизменяемых типов в параметрах функций
  5. Использования в словарях и множествах — только неизменяемые типы

Основное правило: используй неизменяемые типы по умолчанию, а изменяемые только когда это необходимо.