Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое изменяемость типов данных (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+ раз быстрее
Заключение
Понимание изменяемости типов критично для:
- Написания корректного кода — избежание неожиданных изменений
- Производительности — выбор правильных структур данных
- Thread-безопасности — неизменяемые объекты безопаснее в многопоточных приложениях
- Дизайна API — использование неизменяемых типов в параметрах функций
- Использования в словарях и множествах — только неизменяемые типы
Основное правило: используй неизменяемые типы по умолчанию, а изменяемые только когда это необходимо.