Изменяемые и неизменяемые типы данных

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

Оглавление

Что такое изменяемые и неизменяемые объекты в Python

Самое простое определение: объект, внутреннее состояние которого может быть изменено, является изменяемым. И напротив, понятие «неизменяемый объект» подразумевает, что никаких изменений в объекте после его создания быть не может.

Определение изменяемости

Изменяемость сама по себе подразумевает, что что-либо может быть изменено. В Python это означает способность объектов менять свои значения. Как правило, это объекты, в которых хранится набор данных.

Определение неизменяемости

Неизменяемость, в свою очередь, подразумевает, что никакие изменения с течением времени невозможны. Соответственно, если значение объекта не может быть изменено, он называется неизменяемым. После создания таких объектов их значения являются постоянными.

Список изменяемых и неизменяемых объектов

Вот встроенные типы данных Python, объекты которых являются изменяемыми:

  • Списки
  • Множества
  • Словари
  • Определенные пользователем классы (это целиком и полностью зависит от того, как именно пользователь определяет свой класс)

А вот встроенные типы данных Python, объекты которых являются неизменяемыми:

  • Числа
  • Строки
  • Кортежи
  • Замороженные множества (Frozen Sets)
  • Определенные пользователем классы (это целиком и полностью зависит от того, как именно пользователь определяет свой класс)

Изменяемость объектов — одна из характеристик, которая делает Python языком с динамической типизацией. И хотя концепция изменяемости и неизменяемости очень проста, иногда она может сбивать с толку.

Объекты в Python

В Python всё является объектом. И у каждого объекта есть следующие три атрибута:

  • идентичность: адрес в памяти, на который ссылается данный объект
  • тип: тип создаваемого объекта. Например, целое число, список, строка и т. д.
  • значение: значение, которое хранится в данном объекте. Например, список [1, 2, 3] будет содержать числа 1, 2 и 3.

После создания объекта его идентификатор и тип изменить нельзя, а вот значение в изменяемых объектах может быть изменено.

Изменяемые объекты в Python

Вместо того, чтобы углубляться в теоретические аспекты изменяемых и неизменяемых объектов в Python, давайте лучше рассмотрим простой код. Разберем приведенный ниже код шаг за шагом:

# Создание списка, содержащего названия индийских городов
cities = ['Delhi', 'Mumbai', 'Kolkata']

# Вывод в консоль элементов из списка городов, разделенных запятой и пробелом
for city in cities:
    print(city, end=', ')
# Результат [1]: Delhi, Mumbai, Kolkata

# Вывод в консоль местоположения созданного объекта в адрес памяти в шестнадцатеричном формате
print(hex(id(cities)))
# Результат [2]: 0x1691d7de8c8

# Добавление нового города в список городов
cities.append('Chennai')

# Вывод в консоль элементов из списка городов, разделенных запятой и пробелом
for city in cities:
    print(city, end=', ')
# Результат [3]: Delhi, Mumbai, Kolkata, Chennai

# Вывод в консоль местоположения созданного объекта в адрес памяти в шестнадцатеричном формате
print(hex(id(cities)))
# Результат [4]: 0x1691d7de8c8

Приведенный выше пример показывает, что мы смогли изменить внутреннее состояние объекта cities, добавив к нему еще один город Chennai, однако адрес памяти объекта не изменился. Это подтверждает тот факт, что мы изменили уже существующий объект, а не создали новый. Следовательно, можно заключить, что объект типа список (list), ссылка на который заключена в переменной cities, является изменяемым.

Давайте теперь обсудим, что означает неизменяемый объект. Мы уже поняли, что означает изменяемый объект, и очевидно, что в определение неизменяемого объекта будет включена приставка «не». 

Вот простейшее определение неизменяемого объекта: объект, внутреннее состояние которого изменить нельзя, является неизменяемым.

И еще, идентифицировать неизменяемые объекты в Python можно, если сконцентрироваться на сообщениях об ошибках, которые позволяет видеть ваша среда программирования (IDE). Например, рассмотрим приведенный ниже код и связанное с ним сообщение об ошибке при попытке изменить значение элемента кортежа с индексом 0.

# Создание кортежа с именем переменной «foo»
foo = (1, 2)

# Изменение значения foo[0] с 1 на 3
foo[0] = 3
	
# Результат: 
# TypeError: 'tuple' object does not support item assignment 

Неизменяемые объекты в Python

Опять же, простой код лучше всего поможет нам понять, что такое неизменяемый объект. Следовательно, давайте обсудим его шаг за шагом:

# Создание кортежа, содержащего английские названия дней недели
weekdays = 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'

# Вывод в консоль элементов кортежа weekdays
print(weekdays)
# Результат [1]:  ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday')

# Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате
print(hex(id(weekdays)))
# Результат [2]: 0x1691cc35090

# Кортежи являются неизменяемыми, поэтому вы не можете добавлять новые элементы. Поэтому попробуем добавить новый воображаемый день в кортеж weekdays, используя слияние кортежей, при помощи оператора +=
weekdays += 'Pythonday',

# Вывод в консоль элементов кортежа weekdays
print(weekdays)
# Результат [3]: ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Pythonday')

# Вывод в консоль местоположения созданного объекта в  памяти в шестнадцатеричном формате
print(hex(id(weekdays)))
#Результат [4]: 0x1691cc8ad68

Как видите, при добавлении нового элемента в кортеж мы смогли воспользоваться тем же именем переменной, которое ссылалось на старый кортеж, состоящий из семи элементов. Однако идентификатор, который показывает расположение в памяти объекта кортежа, не остался неизменным. Нам не удалось изменить внутреннее состояние объекта weekdays.

Программный менеджер Python создал в памяти новый объект, и переменная weekdays стала ссылаться на него, то есть на новый объект с восемью элементами. Следовательно, мы можем заключить, что объект типа кортеж, на который ссылается переменная weekdays, является неизменяемым.

Где используются изменяемые и неизменяемые объекты?

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

Неизменяемость позволяет создавать множество полезных приложений для различных важных задач, которые, например, мы выполняем в сетевой среде, где допускается параллельная обработка. Создавая неизменяемые объекты, вы запечатываете их значения и гарантируете, что никакие потоки не смогут вызвать перезапись или обновление ваших данных. Это также полезно в ситуациях, когда вы хотите написать фрагмент кода, который нельзя изменить. Например, код отладки, который пытается найти значение неизменяемого объекта.

Нетранзитивный характер неизменяемости

Итак, мы разобрались, что собой представляют изменяемые и неизменяемые объекты в Python. Давайте теперь рассмотрим комбинацию этих двух типов данных. Рассмотрим, что будет происходить, если взять неизменный тип данных, состоящий из элементов, которые являются изменяемыми объектами. Или наоборот.

Как обычно, разберем это на конкретном примере кода:

# Создаем кортеж (неизменяемый объект), который содержит в себе 2 списка (изменяемых объекта) в качестве элементов
# Элементы списков содержат имя, возраст и пол
person = (['Ayaan', 5, 'Male'], ['Aaradhya', 8, 'Female'])

# Выводим кортеж в консоль
print(person)
# Результат [1]: (['Ayaan', 5, 'Male'], ['Aaradhya', 8, 'Female'])

# Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате
print(hex(id(person)))
# Результат [2]: 0x1691ef47f88

# Изменяем возраст для 1-го элемента кортежа. Выбираем 1-й элемент кортежа с использованием индексации [0], а затем 2-ой элемента списка с использованием индексации [1] и присваиваем ему нового значения для возраста равное 4
person[0][1] = 4

# Вывод в консоль обновленного кортежа
print(person)
# Результат [3]: (['Ayaan', 4, 'Male'], ['Aaradhya', 8, 'Female'])

# Вывод местоположения созданного объекта в памяти в шестнадцатеричном формате
print(hex(id(person)))
# Результат [4]: 0x1691ef47f88

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

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

# Создаем список (изменяемый объект), который содержит кортежи (неизменяемые объекты) в качестве элементов
list1 = [(1, 2, 3), (4, 5, 6)]

# Выводим список в консоль
print(list1)
# Результат [1]: [(1, 2, 3), (4, 5, 6)]

# Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате
print(hex(id(list1)))
#Результат [2]: 0x1691d5b13c8

# Меняем первый элемент списка на другой кортеж
list1[0] = (7, 8, 9)

# Выводим список в консоль
print(list1)
# Результат [3]: [(7, 8, 9), (4, 5, 6)]

# Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате
print(hex(id(list1)))
# Результат [4]: 0x1691d5b13c8

Как видите, выбор комбинации изменяемых и неизменяемых объектов полностью остается за вами. Мы надеемся, что изложенная нами информация поможет вам в дальнейшем при выборе типов объектов.

Перед тем как мы закончим изучение неизменяемых объектов, рассмотрим одно исключение из общих правил. Результаты будут несколько неожиданными:

# Создание объекта целочисленного типа со значением 10 и именем переменной ‘x’, которая ссылается на этот объект
x = 10

# вывод в консоль значения ‘x’
print(x)
# Результат [1]: 10

# Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате
print(hex(id(x)))
# Результат [2]: 0x538fb560

# Cоздание объекта целочисленного типа со значением 10 и именем переменной ‘y’, которая ссылается на этот объект
y = 10

# Вывод в консоль значения ‘y’
print(y)
# Результат [3]: 10

# Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате
print(hex(id(y)))
# Результат [4]: 0x538fb560

Согласно нашему пониманию, адрес объектов, находящихся в переменных x и y, должен быть разным, поскольку значение 10 — объект класса Integer, который является неизменяемым. Однако, как видно из кода выше, адрес у них один и тот же. Это не совсем то, что мы ожидали. Следовательно, существуют исключения из правил.

Неизменяемость кортежа

Кортежи относятся к неизменяемому типу данных и, следовательно, не могут быть изменены после их создания в Python. Это потому, что они поддерживают те же последовательности операций, что и строки. А мы все знаем, что строки являются неизменяемыми объектами. С помощью оператора индекса можно выбрать элемент кортежа точно так же, как и в строке. Таким образом, кортежи неизменяемы.

Исключения в неизменяемости

Как и везде, в языке Python также существуют исключения из правил. В том числе они касаются и понятия неизменяемости. Некоторые неизменяемые объекты в действительности могут быть изменены. Это несколько сбивает с толку. Чтобы разобраться, давайте рассмотрим простой пример.

Допустим, у нас есть кортеж под именем tup.

tup = ('GreatLearning', [4,3,1,2])

Мы видим, что в этом кортеже есть элементы разных типов данных. Первым элементом здесь является строка, которая, как хорошо известно, неизменяема по своей природе. А второй элемент — список, который является изменяемым типом данных, что также общеизвестно.

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

FAQ

1. В чем разница между изменяемыми и неизменяемыми объектами в Python?

Изменяемый объектНеизменяемый объект
Состояние объекта может быть изменено после его создания.Состояние объекта не может быть изменено после его создания.
Объект не потокобезопасен.Объект потокобезопасен.
Изменяемые классы не являются окончательными.При создании неизменяемого объекта важно сделать его окончательным, например, при помощи модификатора Final.

2. Что такое изменяемые и неизменяемые типы данных в Python?

  • Изменяемыми типами данных в Python являются: списки, словари, множества и определенные пользователем классы
  • Неизменяемыми типами данных в Python являются: числа (int, float, decimal), логический тип данных (bool), строки (string), кортежи (tuple), range

3. Являются ли списки изменяемыми типами данных в Python?

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

Примеры, связанные со списками, разбирались ранее в данной статье.

4. Почему кортежи относятся к неизменяемым типам данных в Python?

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

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

5. Являются ли множества изменяемым типом данных?

Множество (set) — это итерируемая неупорядоченная коллекция, которую можно использовать для выполнения математических операций (таких как объединение, пересечение, разность и т. д.). Каждый элемент в этой коллекции уникален и неизменен, т. е. не должно быть повторяющихся значений, и значения нельзя изменить. Однако мы можем добавлять или удалять элементы из множества, так как само множество является изменяемым объектом.

6. Являются ли строки изменяемым типом данных?

Строки в Python являются неизменяемыми объектами. Это означает, что их значение не может быть обновлено.

Перевод статьи Understanding Mutable and Immutable in Python.