Мутабельность и переработка объектов в Python

RedDeveloper
30.01.2023 12:31
Мутабельность и переработка объектов в Python

Краткое описание того, как объекты ссылаются, изменяются и перерабатываются в Python

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

В этой статье мы рассмотрим эти темы:

  • Что такое переменные в Python?
  • Мелкие и глубокие копии объектов
  • Сборка мусора в Python

Переменные - это не коробки

Обычно переменные рассматриваются как коробки или контейнеры, что мешает пониманию ссылочных переменных в объектно-ориентированных языках.

Переменные Python похожи на ссылочные переменные в Java, вы можете думать о них как о метках с именами, прикрепленными к объектам.

var_one = [1, 2, 3]
var_two = var_one
var_one.append(4)
print(var_two)
# [1, 2, 3, 4]

В этом примере мы изменяем список, на который ссылается "var_one", добавляя еще один элемент. Когда мы печатаем "var_two", мы получаем тот же список.

Это означает, что "var_two" ссылается на тот же список, на который ссылается "var_one".

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

Это приводит нас к другим вопросам! Как проверить, равны ли два объекта?

В Python каждый объект имеет свой id, и его можно получить с помощью функции id(obj). Теперь две переменные, ссылающиеся на один и тот же объект, будут иметь одинаковый id, то есть id - это адрес памяти объекта, и он уникален в течение жизненного цикла объекта.

charles = {'name': 'Charles L. Dodgson', 'born': 1832}
lewis = charles
print(lewis is charles)
# True

print(id(charles), id(lewis))
# (4300473992, 4300473992)

lewis['balance'] = 950
print(charles)
# {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}

alex = {'name': 'Charles L. Dodgson', 'born': 1832, 'balance': 950}
print(alex == charles)
# True

print(alex is not charles)
# False
Чарльз и льюис привязаны к одному объекту, а алекс - к отдельному объекту равной ценности
Чарльз и льюис привязаны к одному объекту, а алекс - к отдельному объекту равной ценности

Операторы is и not сравнивают идентичность двух объектов, а функция

Id() возвращает целое число, представляющее идентичность.

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

Оператор is не может быть перегружен и, как правило, работает быстрее, чем оператор ==, поскольку его можно перегрузить.

Большинство встроенных типов и объектов Python переопределяют специальный метод __eq__ для поддержки оператора ==.

Мелкое копирование VS глубокое копирование

Мелкие копии делать проще всего, но это может быть не то, что вам нужно.

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

Это экономит память и не вызывает проблем, если все объекты неизменяемы, но если есть изменяемые объекты, это может привести к неприятным сюрпризам.

list_1 = [3, [66, 55, 44], (7, 8, 9)]
list_2 = list(list_1)
Визуализация в Python Tutor
Визуализация в Python Tutor

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

list_1.append(100)
list_1[1].remove(55)
print('list_1:', list_1)
# list_1: [3, [66, 44], (7, 8, 9), 100]
print('list_2:', list_2)
# list_2: [3, [66, 44], (7, 8, 9)]
list_2[1] += [33, 22]
list_2[2] += (10, 11)
print('list_1:', list_1)
# list_1: [3, [66, 44, 33, 22], (7, 8, 9), 100]
print('list_2:', list_2)
# list_2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]
Последнее состояние выполнения кода примера
Последнее состояние выполнения кода примера

Несколько моментов, которые следует иметь в виду:

  • Любые операции над встроенными объектами будут видны другим переменным, ссылающимся на эти объекты, если только этот объект не является Immutable.
  • являются неизменяемыми, поэтому любая операция над ними создает новый кортеж и перепривязывает его к переменной, как в примере.

Глубокое копирование на помощь

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

В стандартной библиотеке есть модуль, который реализует это, он предлагает две функции copy() и deepcopy(), первая - для поверхностного копирования, а вторая - для глубокого.

import copy
 
class Bus:
    def __init__(self, passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = list(passengers)
   
    def pick(self, name):
        self.passengers.append(name)

    def drop(self, name):
        self.passengers.remove(name)

bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])
bus2 = copy.copy(bus1)
bus3 = copy.deepcopy(bus1)

print(id(bus1), id(bus2), id(bus3))
# (4301498296, 4301499416, 4301499752)

bus1.drop('Bill')

print(bus2.passengers)
# ['Alice', 'Claire', 'David']

print(id(bus1.passengers), id(bus2.passengers), id(bus3.passengers))
# (4302658568, 4302658568, 4302657800)

print(bus3.passengers)
# ['Alice', 'Bill', 'Claire', 'David']

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

Решением является реализация собственных специальных методов __copy__ и __deepcopy__.

Сборка мусора

Объекты в Python никогда не уничтожаются явно, как, например, в C#, они собираются в мусор, когда становятся недоступными.

У нас есть оператор, который удаляет ссылки, del, но никогда - объекты.

a = [1, 2]
b = a
del a
print(b)
# [1, 2]
 b = [3]
# The original object is now ready to be garbage collected

'сборщик мусора выбросит объект из памяти, если на него нет ссылок.

Дальнейшее чтение

  • 6-я глава книги Fluent Python
  • Официальная документация Python по модулю copy
  • Глава "Модель данных" в книге The Python Language Reference

Предыдущая статья в серии: Классы данных и как их создавать

Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit

20.03.2023 14:01

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

Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра

20.03.2023 12:24

Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие действия:

ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023

20.03.2023 11:15

О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц. HTML обеспечивает структуру страницы CSS (визуальное и звуковое) оформление для различных устройств. Наряду с графикой и сценариями HTML и CSS являются...

Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular

20.03.2023 08:46

Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?

Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы

19.03.2023 13:43

Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем запускать наши php-файлы через localhost на наших компьютерах. Мне с трудом удалось установить его и я решил поделиться этой статьей, чтобы помочь тем,...

Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах

19.03.2023 13:03

При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после поставщика контекста. Это позволит избежать ненужных повторных рендеров.