Изменяемые и неизменяемые типы данных: в чём разница

Изменяемые и неизменяемые типы данных: в чём разница Переменные и типы данных

Привет! Ты уже знаешь, что в Python есть числа, строки, списки, словари. Но есть одно важное свойство, которое делит все типы данных на две группы: изменяемые и неизменяемые.

Понимание этой разницы поможет тебе избегать многих ошибок, особенно при работе с функциями и копированием данных.

В этой статье мы разберём:

  • Что значит «изменяемый» и «неизменяемый»
  • Какие типы к какой группе относятся
  • Как это влияет на присваивание и копирование
  • Подводные камни и как их избежать

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный Python
  • Базовое понимание переменных, списков и словарей

Совет: Открой VS Code и повторяй примеры. Разницу легче понять на практике.

Основная часть

Что такое изменяемые и неизменяемые типы

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

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

ГруппаТипы данных
Неизменяемые (immutable)intfloatstrbooltupleNonefrozenset
Изменяемые (mutable)listdictset (множества)

Пример с неизменяемым типом (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)intfloatstrbooltuple
  • Изменяемые (mutable)listdictset
  • При присваивании изменяемых объектов обе переменные ссылаются на один объект
  • Функции могут изменять переданные изменяемые объекты
  • Осторожно с аргументами по умолчанию и вложенными структурами

КВИЗ

Что дальше?

Следующая статья: «Множественное присваивание: a, b = 1, 2»

Оцените статью
IMI-DS - PYTHON LERNEN
Содержание
Оглавление ×