Найти тему
Simple Prog

Переменные класса против переменных экземпляра. ООП в Python

Оглавление

Разбираемся в разнице

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

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

Поэтому мои первые эксперименты с ООП были пронизаны удивительными линиями поведения и странными ошибками. В этой статье мы устраним путаницу относительно этой темы при помощи нескольких практических примеров.

Источник: https://unsplash.com/photos/unwXUc_Pqi4
Источник: https://unsplash.com/photos/unwXUc_Pqi4

Как я писал ранее, в объектах Python объявляются два вида атрибутов данных: переменные класса (class variables) и переменные экземпляра (instance variables):

Переменные класса

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

Переменные экземпляра

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

Примеры кода

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

Что собаке для счастья нужно? Правильно! Четыре лапы да имя (ну может еще хвост):

Oкей. У нас есть изящное объектно-ориентированное представление ситуации с собакой, которую я только что описал. Создание новых экземпляров Dog работает, как и ожидалось, и каждый из них получает переменную экземпляра с именем name:

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

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

В этом и заключается ключевое различие между переменными класса и переменными экземпляра:

Ладно, пока все идет неплохо.

Источник: https://unsplash.com/photos/1waoTbCzl8k
Источник: https://unsplash.com/photos/1waoTbCzl8k

Изменение атрибутов

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

Как бы вы представили этот факт в небольшой "песочнице" с исходным кодом, которая у нас сейчас есть?

Первая идея — просто модифицировать переменную num_legs в классе Dog:

>>> Dog.num_legs = 6

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

Это затрагивает всех собак, даже тех, которые были созданы ранее:

Этот вариант не сработал. А не сработал он потому, что модификация переменной класса в пространстве имен класса затрагивает все экземпляры класса.

Давайте отыграем это изменение в переменной класса назад и вместо этого попробуем дать дополнительную пару лап только конкретному псу Джеку:

Так, и что же за чудовище мы получили? Сейчас выясним:

Ладно, выглядит "довольно неплохо" (ну, кроме того, конечно, что мы прямо сейчас дали бедному псу несколько лишних лап). Но как это изменение на самом деле повлияло на наши объекты Dog?

А проблема, как выясняется здесь в следующем: несмотря на то, что мы получили желаемый результат (лишние лапы для Джека), мы внесли переменную экземпляра num_legs в экземпляр с псом по кличке Джек. И теперь новая переменная экземпляра num_legs "оттеняет" переменную класса с тем же самым именем, переопределяя и скрывая ее, когда мы обращаемся к области действия экземпляра:

Как вы видите, переменные класса, казалось бы, стали несогласованными. Это произошло потому, что внесения изменения в jack.num_legs создало переменную экземпляра с тем же самым именем, что и у переменной класса.

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

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

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

Читайте также:

Наука
7 млн интересуются