Привет! Ты уже знаешь, что в Python есть числа, строки, списки, словари. Но есть одно важное свойство, которое делит все типы данных на две группы: изменяемые и неизменяемые.
Понимание этой разницы поможет тебе избегать многих ошибок, особенно при работе с функциями и копированием данных.
В этой статье мы разберём:
- Что значит «изменяемый» и «неизменяемый»
- Какие типы к какой группе относятся
- Как это влияет на присваивание и копирование
- Подводные камни и как их избежать
- Что нужно знать перед началом
- Основная часть
- Что такое изменяемые и неизменяемые типы
- Пример с неизменяемым типом (int)
- Пример с изменяемым типом (list)
- Почему это важно: присваивание
- Пример с изменяемыми типами: словарь
- Строки — неизменяемые (важно!)
- Кортеж (tuple) — неизменяемый список
- Передача в функции
- Как скопировать изменяемый объект
- Задачи для закрепления
- Нюансы и подводные камни
- Изменяемые типы как аргументы по умолчанию
- Вложенные изменяемые объекты
- Неизменяемые типы могут содержать изменяемые
- Частые ошибки и как их избежать
- Ошибка 1: Думать, что строки можно менять как списки
- Ошибка 2: Нечаянное изменение исходного списка в функции
- Ошибка 3: Путаница между присваиванием и копированием
- Шпаргалка
- Заключение / Выводы
- КВИЗ
- Что дальше?
Что нужно знать перед началом
Для этого урока тебе понадобится:
- Установленный Python
- Базовое понимание переменных, списков и словарей
Совет: Открой VS Code и повторяй примеры. Разницу легче понять на практике.
Основная часть
Что такое изменяемые и неизменяемые типы
Неизменяемые (immutable) — это типы, которые нельзя изменить после создания. Любая операция, которая кажется изменением, на самом деле создаёт новый объект.
Изменяемые (mutable) — это типы, которые можно менять без создания нового объекта.
| Группа | Типы данных |
|---|---|
| Неизменяемые (immutable) | int, float, str, bool, tuple, None, frozenset |
| Изменяемые (mutable) | list, dict, set (множества) |
Пример с неизменяемым типом (int)
x = 10
print(id(x)) # какой-то адрес в памяти, например 140736123456
x = x + 5
print(id(x)) # Адрес ИЗМЕНИЛСЯ — это новый объект!Когда ты меняешь число, создаётся новый объект. Старый остаётся в памяти (пока не будет удалён сборщиком мусора).
Пример с изменяемым типом (list)
my_list = [1, 2, 3]
print(id(my_list)) # адрес, например 140736123456
my_list.append(4)
print(id(my_list)) # Адрес НЕ ИЗМЕНИЛСЯ — тот же объект!Список можно менять (добавлять, удалять элементы) без создания нового объекта.
Почему это важно: присваивание
Для неизменяемых типов присваивание копирует ссылку, но при изменении создаётся новый объект.
a = 10
b = a
print(a, b) # 10 10
b = 20
print(a, b) # 10 20 — a не изменилосьДля изменяемых типов изменение через одну переменную видно через другую.
list_a = [1, 2, 3]
list_b = list_a
print(list_a, list_b) # [1, 2, 3] [1, 2, 3]
list_b.append(4)
print(list_a, list_b) # [1, 2, 3, 4] [1, 2, 3, 4] — изменились оба!Важно:
list_aиlist_bссылаются на один и тот же список в памяти. Изменение через любую из переменных меняет общий объект.
Пример с изменяемыми типами: словарь
dict_a = {"name": "Анна", "age": 25}
dict_b = dict_a
dict_b["city"] = "Москва"
print(dict_a) # {'name': 'Анна', 'age': 25, 'city': 'Москва'}
print(dict_b) # {'name': 'Анна', 'age': 25, 'city': 'Москва'}Строки — неизменяемые (важно!)
Многие новички думают, что строки можно менять, но это не так.
s = "Hello"
# s[0] = "h" # Ошибка! TypeErrorЧтобы изменить строку, нужно создать новую:
s = "Hello"
s = "h" + s[1:] # "hello"Совет: Если тебе нужно часто менять строку, лучше использовать список и метод
join().
Кортеж (tuple) — неизменяемый список
Кортеж похож на список, но его нельзя изменить.
my_tuple = (1, 2, 3)
# my_tuple[0] = 10 # Ошибка! TypeErrorЗачем нужны кортежи, если есть списки?
- Кортежи занимают меньше памяти
- Кортежи можно использовать как ключи в словарях (списки — нельзя)
- Они безопаснее, если данные не должны меняться
Передача в функции
Понимание изменяемости особенно важно при передаче аргументов в функции.
Неизменяемые типы (передаются «по значению»):
def change_number(n):
n = n + 5
print("Внутри функции:", n)
x = 10
change_number(x) # Внутри функции: 15
print("Снаружи:", x) # Снаружи: 10 (не изменилось!)Изменяемые типы (передаются «по ссылке»):
def change_list(lst):
lst.append(4)
print("Внутри функции:", lst)
my_list = [1, 2, 3]
change_list(my_list) # Внутри функции: [1, 2, 3, 4]
print("Снаружи:", my_list) # Снаружи: [1, 2, 3, 4] (изменился!)Важно: Если ты не хочешь, чтобы функция изменяла исходный список, передай его копию:
change_list(my_list.copy()).
Как скопировать изменяемый объект
Если нужно создать независимую копию списка или словаря:
# Для списков
original = [1, 2, 3]
copy_1 = original.copy() # Способ 1
copy_2 = original[:] # Способ 2 (срез)
copy_3 = list(original) # Способ 3
# Для словарей
dict_original = {"a": 1, "b": 2}
dict_copy = dict_original.copy()
# Проверяем
copy_1.append(4)
print(original) # [1, 2, 3] — не изменился!
print(copy_1) # [1, 2, 3, 4]Совет: Для вложенных структур (список списков) нужна глубокая копия из модуля
copy.
Задачи для закрепления
Попробуй решить задачи самостоятельно. Ответы ниже, но сначала попробуй сам.
Задача 1. Что выведет код?
Задача 2. Почему код вызывает ошибку? Как исправить?
Задача 3. Что выведет код?
Задача 4. Как создать независимую копию списка?
Задача 5. Является ли кортеж изменяемым или неизменяемым? Что будет при попытке изменить его элемент?
Ответы
Задача 1
# [1, 2, 3, 4]
# a и b ссылаются на один список, append изменяет его.Задача 2
# Строки неизменяемы (immutable)
# Исправление:
s = "Python"
s = "p" + s[1:] # "python"Задача 3
# 5 6
# x и y сначала ссылаются на один объект 5,
# но при изменении создаётся новый объект для y.Задача 4
new_list = original.copy()
# или
new_list = original[:]
# или
new_list = list(original)Задача 5
# Кортеж неизменяемый (immutable).
# При попытке изменить элемент (my_tuple[0] = 1) будет TypeError.Нюансы и подводные камни
Изменяемые типы как аргументы по умолчанию
Это классическая ошибка новичков:
def add_item(item, my_list=[]):
my_list.append(item)
return my_list
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] — неожиданность!Список по умолчанию создаётся один раз при определении функции и переиспользуется.
Как исправить:
def add_item(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list
print(add_item(1)) # [1]
print(add_item(2)) # [2] — теперь правильноВложенные изменяемые объекты
При поверхностном копировании (.copy(), [:]) копируется только первый уровень. Внутренние списки остаются общими.
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] — изменился!Глубокая копия:
import copy
deep = copy.deepcopy(original)Неизменяемые типы могут содержать изменяемые
Кортеж неизменяем, но его элементы могут быть изменяемыми!
my_tuple = ([1, 2], [3, 4])
my_tuple[0].append(99) # OK — список изменяемый
# my_tuple[0] = [5, 6] # Ошибка! Нельзя изменить сам кортежЧастые ошибки и как их избежать
Ошибка 1: Думать, что строки можно менять как списки
Неправильно:
s = "hello"
s[0] = "H"Правильно:
s = "hello"
s = s.capitalize()Ошибка 2: Нечаянное изменение исходного списка в функции
Неправильно:
def double_list(lst):
for i in range(len(lst)):
lst[i] = lst[i] * 2
numbers = [1, 2, 3]
double_list(numbers)
print(numbers) # [2, 4, 6] — исходный список изменилсяПравильно (если не нужно менять):
def double_list(lst):
return [x * 2 for x in lst]
numbers = [1, 2, 3]
result = double_list(numbers)
print(numbers) # [1, 2, 3] — не изменился
print(result) # [2, 4, 6]Ошибка 3: Путаница между присваиванием и копированием
a = [1, 2, 3]
b = a # Это не копия! Это та же самая переменная
b.append(4) # a тоже изменитсяКак исправить: Используй b = a.copy() для создания копии.
Шпаргалка
| Тип | Изменяемый? | Можно изменить |
|---|---|---|
int | Нет | Нельзя |
float | Нет | Нельзя |
str | Нет | Нельзя |
bool | Нет | Нельзя |
tuple | Нет | Нельзя |
list | Да | Можно |
dict | Да | Можно |
set | Да | Можно |
| Действие | Результат |
|---|---|
b = a (для списка) | Ссылка на тот же объект |
b = a.copy() | Поверхностная копия |
b = copy.deepcopy(a) | Глубокая копия |
b = a (для числа) | Ссылка на тот же объект, но при изменении создаётся новый |
Заключение / Выводы
Сегодня мы узнали:
- Неизменяемые (immutable):
int,float,str,bool,tuple - Изменяемые (mutable):
list,dict,set - При присваивании изменяемых объектов обе переменные ссылаются на один объект
- Функции могут изменять переданные изменяемые объекты
- Осторожно с аргументами по умолчанию и вложенными структурами








