Python — это объектно-ориентированный язык программирования, но он может вести себя странно. Если вы пришли из других языков ООП, этот пост может быть вам полезен

В главе 8 книги Fluent Python Лучано Рамальо обсуждает, как объекты python находятся под капотом. Здесь будет определена фундаментальная концепция хранения переменных в python и рассмотрены некоторые соответствующие примечания.

Без дальнейших церемоний, давайте погрузимся.

1 — Переменные Python — это не блоки

Совет. Переменные Python — это метки для значений.

В программировании 101 нас часто учат, что переменные — это ящики, в которых хранится значение. Например, в ящике a хранится список [1,2,3]. Однако в питоне это не так.

На рисунке 1 имена переменных (синий цвет) являются «метками» для расположения в памяти. Обратите внимание, что у одного объекта может быть несколько имен переменных, как показано слева. Ячейки в памяти (черные) — это «ящик», а не само название. В этих полях хранятся значения (красные). Давайте рассмотрим пример.

var_name = 1
var_name         # 1
id(var_name)     # 4443642160

В приведенном выше фрагменте переменной var_name присваивается значение 1. «Ящик», в котором хранится значение, — это идентификатор памяти 4443642160.

Теперь давайте добавим еще одну переменную в то же самое место в памяти…

another_var_name = var_name
id(var_name)                            # 4443642160
var_name                                # 1
id(var_name) == id(another_var_name)    # True

Как мы видим, у нас есть две переменные Python, которые ссылаются на один и тот же объект. Однако, как только мы изменим нашу вторую переменную, например, запустив y += 1, наша ячейка памяти изменится на 4443642192, что сделает объекты другими.

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

2 — Объектное равенство

Совет. Используйте ==, чтобы определить, имеют ли две переменные одинаковые значения, и is, чтобы определить, имеют ли две переменные общие местоположения в памяти.

Учитывая настройку в разделе 1, этот раздел будет кратким.

== определяет, имеют ли переменные одинаковое значение — это похоже на функцию .equals() в java. Однако, если вы действительно хотите определить, являются ли переменные одним и тем же объектом, вам следует использовать is для проверки идентификаторов памяти.

a = 1
b = 1
c = a
a == b == c      # True
a is c           # True

А теперь интересное примечание. Мы ожидаем, что a is b будет оцениваться как False. Однако иногда это True.

Удивительно, но для экономии места Python иногда присваивает переменные одному и тому же объекту, если значения объектов совпадают. Это экономит память, потому что в нашем случае вам не нужно создавать отдельные экземпляры целого числа 1 — вы можете просто ссылаться на один и тот же объект.

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

3 — Копирование против глубокого копирования

Совет: если вы не знаете, что делаете, используйте deepcopy().

Если вы работали с пандами, вы, вероятно, видели и/или использовали df.copy(). Использование copy вместо deepcopy влияет только на работу с составными объектами (объектами, которые содержат другие объекты, такие как списки или экземпляры классов). Но для некоторых парадигм программирования составные объекты являются обычным явлением.

Вот разница между глубоким копированием и поверхностным копированием.

  • Глубокая копия создает новый составной объект, а затем рекурсивно вставляет в него копии объектов, найденных в оригинале.
  • Вповерхностной копии создается новый составной объект, а затем (насколько это возможно) вставляется в него ссылки на объекты, найденные в оригинале.

Пример должен помочь прояснить это…

Класс Bus создает список пассажиров, тем самым делая каждый экземпляр класса составным объектом.

Как мы видим, bus2 — это поверхностная копия bus1, а bus3 — глубокая копия. Когда объекты в bus1 будут изменены, это повлияет на bus2. Однако bus3 совершенно не пострадал.

Вот почему безопаснее использовать deepcopyвы получите совершенно новую копию значений объекта.

4 — Функциональные параметры

Совет: будьте осторожны, если вы установили пустую изменяемую переменную в качестве функции Python по умолчанию.

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

Если вы передадите пустой список по умолчанию, например def my_func(x=[]):, а затем измените x, вы будете редактировать встроенную функциональность list(), а не переменную x.

Рекомендуемый метод в книге — указать значения по умолчанию в начале функции через логику случая…

def my_func(x):
  if x is None:
    x = []

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

5 — Сбор мусора

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

В качестве последнего совета давайте поговорим о том, как работает сборка мусора в Python.

Каждый раз, когда мы присваиваем объекту имя переменной, мы даем ему ссылку. Таким образом, a=1; b=a означает, что ячейка памяти, в которой хранится 1, имеет счетчик ссылок, равный 2.

Теперь предположим, что мы присваиваем a и b None, например. a=None; b=None. Теперь, поскольку нет переменных, ссылающихся на объект со значением 1, эта область памяти может быть удалена сборщиком мусора.

Если счетчик ссылок равен 0, ячейка памяти стирается для повторного использования.

Довольно просто, правда?

Теперь, чтобы завершить это, давайте также поговорим о ключевом слове del. del используется для удаления имен переменных, а не самого объекта. Удаляя имя нашей переменной, мы освобождаем это имя для последующего использования и тем самым избегаем возможных конфликтов имен. Однако на бэкенде, если del приводит к тому, что счетчик ссылок становится равным 0, объект также будет удален.

Спасибо за чтение! Я напишу еще 16 постов, посвященных академическим исследованиям в индустрии DS. В моем комментарии есть ссылки на основной источник этого поста и некоторые полезные ресурсы.