Переменные класса объявляются внутри определения класса (но
за пределами любых методов экземпляра). Они не привязаны ни к од-
ному конкретному экземпляру класса. Вместо этого переменные клас-
са хранят свое содержимое в самом классе, и все объекты, созданные
на основе того или иного класса, предоставляют общий доступ к одина-
ковому набору переменных класса. Например, это означает, что моди-
фикация переменной класса одновременно затрагивает все экземпляры
объекта.
Переменные экземпляра всегда привязаны к конкретному экземпляру
объекта. Их содержимое хранится не в классе, а в каждом отдельном
объекте, созданном на основе класса. По этой причине содержимое пере-
менной экземпляра абсолютно независимо от одного экземпляра объекта
4 .7 . Переменные класса против переменных экземпляра: подводные камни 137
к другому. И поэтому модификация переменной экземпляра одновремен-
но затрагивает только один экземпляр объекта.
Ладно, все это было довольно абстрактно — самое время рассмотреть
немного исходного кода! Давайте потренируемся на собачках… В обу-
чающих пособиях, посвященных ООП, для иллюстрации этого тезиса
всегда используются автомобили или домашние животные, и мне сложно
отказаться от этой традиции.
Что собаке для счастья нужно? Правильно! Четыре лапы да имя:
class Dog:
num_legs = 4 # <- Переменная класса
def __init__(self, name):
self.name = name # <- Переменная экземпляра
О’кей. У нас есть изящное объектно-ориентированное представление
ситуации с собакой, которую я только что описал. Создание новых экзем-
пляров
Dog
работает, как и ожидалось, и каждый из них получает пере-
менную экземпляра с именем
name
:
>>> jack = Dog('Джек')
>>> jill = Dog('Джилл')
>>> jack.name, jill.name
('Джек', 'Джилл')
Во всем, что касается переменных класса, всегда есть чуть больше гибко-
сти. Доступ к переменной класса
num_legs
можно получить либо непо-
средственно в каждом экземпляре
Dog
, либо в самом классе:
>>> jack.num_legs, jill.num_legs
(4, 4)
>>> Dog.num_legs
4
Однако попытка получить доступ к переменной экземпляра через класс
потерпит неудачу с исключением
AttributeError
. Переменные экземпля-
ра характерны для каждого экземпляра объекта и создаются, когда выпол-
няется конструктор
__init__
— они даже не существуют в самом классе.
138 Глава 4 • Классы и ООП
В этом заключается ключевое различие между переменными класса
и переменными экземпляра:
>>> Dog.name
AttributeError:
"type object 'Dog' has no attribute 'name'"
Ладно, пока все идет неплохо.
Допустим, в один прекрасный день пес по кличке Джек поедал свой ужин,
расположившись слишком близко от микроволновки, в результате у него
выросла дополнительная пара лап. Как бы вы представили этот факт в не-
большой «песочнице» с исходным кодом, которая у нас сейчас есть?
Первая идея — просто модифицировать переменную
num_legs
в классе
Dog
:
>>> Dog.num_legs = 6
Но помните, мы не хотим, чтобы все собаки стали носиться вокруг о шести
лапах. Итак, сейчас мы только что превратили каждую собаку в нашей ми-
кровселенной в сверхсобаку, потому что мы модифицировали переменную
класса. Это затрагивает всех собак, даже тех, которые были созданы ранее:
>>> jack.num_legs, jill.num_legs
(6, 6)
Этот вариант не сработал. А не сработал он потому, что модификация
переменной класса в пространстве имен класса затрагивает все экзем-
пляры класса. Давайте отыграем это изменение в переменной класса
назад и вместо этого попробуем дать дополнительную пару лап только
конкретному псу Джеку:
>>> Dog.num_legs = 4
>>> jack.num_legs = 6
Так, и что за чудовище мы получили? Сейчас выясним:
>>> jack.num_legs, jill.num_legs, Dog.num_legs
(6, 4, 4)
4 .7 . Переменные класса против переменных экземпляра: подводные камни 139
Ладно. Выглядит «довольно неплохо» (ну, кроме того, конечно, что мы
прямо сейчас дали бедному псу несколько лишних лап). Но как это из-
менение на самом деле повлияло на наши объекты
Dog
?
А проблема, как выясняется, здесь в следующем: несмотря на то что мы
получили желаемый результат (лишние лапы для Джека), мы внесли
переменную экземпляра
num_legs
в экземпляр с псом по кличке Джек.
И теперь новая переменная экземпляра
num_legs
«оттеняет» переменную
класса с тем же самым именем, переопределяя и скрывая ее, когда мы об-
ращаемся к области действия экземпляра:
>>> jack.num_legs, jack.__class__.num_legs
(6, 4)
Как вы видите, переменные класса, казалось бы, стали несогласованными.
Это произошло потому, что внесение изменения в
jack.num_legs
созда-
ло переменную экземпляра с тем же самым именем, что и у переменной
класса.
Это не всегда плохо, но важно понимать, что именно здесь произошло.
Прежде чем я наконец-то разобрался в области действия уровня класса
и уровня экземпляра в Python, это было широкими воротами, через кото-
рые в мои программы то и дело закрадывались ошибки.
Сказать по правде, попытка модифицировать переменную класса через
экземпляр объекта, который затем непредумышленно создает переменную
экземпляра с тем же именем, затеняя оригинальную переменную класса,
является в Python чем-то вроде подводного камня ООП.
Пример без собак
Хотя в процессе написания этого раздела книги ни одна собака не по-
страдала (это все шутки и игры до тех пор, пока кто-то не вырастит себе
лишнюю пару лап), я хочу дать вам еще один практический пример
полезных штук, которые вы можете сделать с переменными класса. То,
что будет немного ближе к реальным приложениям с переменными
класса.
140 Глава 4 • Классы и ООП
Итак, вот этот пример. Приведенный ниже класс
CountedObject
отсле-
живает, сколько раз он использовался для создания экземпляров на про-
тяжении жизни программы (что на деле может обеспечить интересный
метрический показатель производительности):
class CountedObject:
num_instances = 0
def __init__(self):
self.__class__.num_instances += 1
Класс
CountedObject
содержит переменную класса
num_instances
, которая
служит в качестве общего счетчика. Когда класс объявлен, он инициали-
зирует счетчик нулем, а затем оставляет его в покое.
Всякий раз, когда вы создаете новый экземпляр этого класса, он увели-
чивает общий счетчик на единицу во время выполнения конструктора
__init__
:
>>> CountedObject.num_instances
0
>>> CountedObject().num_instances
1
>>> CountedObject().num_instances
2
>>> CountedObject().num_instances
3
>>> CountedObject.num_instances
3
Обратите внимание, как этот фрагмент кода должен проскакивать через
небольшой обруч, чтобы обеспечить увеличение переменной счетчика
Do'stlaringiz bilan baham: |