в классе. Легко можно было бы сделать ошибку, если бы я написал кон-
структор следующим образом:
# ПРЕДУПРЕЖДЕНИЕ: Эта реализация содержит ошибку
class BuggyCountedObject:
num_instances = 0
def __init__(self):
self.num_instances += 1 # !!!
4 .7 . Переменные класса против переменных экземпляра: подводные камни 141
Как вы увидите, эта (плохая) реализация никогда не будет увеличивать
общую переменную счетчика:
>>> BuggyCountedObject.num_instances
0
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject().num_instances
1
>>> BuggyCountedObject.num_instances
0
Уверен, что вы увидели, где я допустил промах. Эта (ошибочная) реализа-
ция не увеличивает общий счетчик, потому что я сделал ошибку, которую
объяснил в предыдущем примере с псом Джеком. Эта реализация не будет
работать, потому что я непредумышленно «затенил» переменную класса
num_instance
, создав в конструкторе переменную экземпляра с тем же
именем.
Она правильно вычисляет новое значение счетчика (перейдя от 0 к 1),
но затем сохраняет результат в переменной экземпляра, а это означает,
что другие экземпляры класса никогда не увидят обновленное значение
счетчика.
Как вы видите, допустить эту ошибку очень легко. Во время работы с раз-
деляемым состоянием в классе следует быть осторожным и перепроверять
области действия. Автоматизированные тесты и контроль качества работы
со стороны коллег существенно помогают в этом.
Однако надеюсь, что вы видите, почему и как переменные класса (несмо-
тря на их подводные камни) могут оказаться полезными инструментами
на практике. Удачи!
Ключевые выводы
Переменные класса предназначены для данных, совместно используе-
мых всеми экземплярами класса. Они принадлежат именно классу, а не
142 Глава 4 • Классы и ООП
конкретному экземпляру и являются общими для всех экземпляров
класса.
Переменные экземпляра предназначены для данных, которые уникаль-
ны для каждого экземпляра. Они принадлежат отдельным экземплярам
объекта и не являются общими для других экземпляров класса. Каждая
переменная экземпляра получает уникальное резервное хранилище,
характерное для данного экземпляра.
Поскольку переменные класса могут быть «затенены» переменными
экземпляра, имеющими одинаковое имя, можно легко (непреднаме-
ренно) переопределить переменные класса, в результате чего будут
внесены ошибки и создано странное поведение.
4 .8 . Срыв покровов с методов экземпляра,
методов класса и статических методов
В этой главе вы увидите, что именно в Python стоит за методами класса
(class methods), статическими методами (static methods) и обычными
методами экземпляра (instance methods).
Если вы разовьете интуитивное понимание их различий, то сможете писать
объектно-ориентированный программный код Python, который яснее со-
общает свой замысел и в конечном счете будет удобнее в сопровождении.
Давайте начнем с написания класса (Python 3), который содержит про-
стые примеры всех трех типов методов:
class MyClass:
def method(self):
return 'вызван метод экземпляра', self
@classmethod
def classmethod(cls):
return 'вызван метод класса', cls
@staticmethod
def staticmethod():
return 'вызван статический метод'
4 .8 . Срыв покровов с методов экземпляра 143
Примечание для пользователей Python 2: декораторы
@staticmethod
и
@
classmethod
доступны, начиная с Python 2.4, и поэтому данный пример
будет работать как есть. Вместо того чтобы использовать простое объявле-
ние
class
MyClass
, вы можете объявить класс в новом стиле, с наследова-
нием от
object
с помощью синтаксической конструкции
MyClass(object)
.
Но в остальном все в шоколаде!
Методы экземпляра
Первый метод в
MyClass
с именем
method
является обычным методом
экземпляра. Это базовый, без наворотов, тип метода, который вы будете
использовать большую часть времени. Вы видите, что этот метод прини-
мает один параметр,
self
, который указывает на экземпляр класса
MyClass
во время вызова этого метода. Но, разумеется, методы экземпляра могут
принимать более одного параметра.
Через параметр
self
методы экземпляра могут свободно получать доступ
к атрибутам и другим методам в том же самом объекте. Это придает им
большую мощь в том, что касается модификации состояния объекта.
Методы экземпляра могут не только модифицировать состояние объекта,
но и получать доступ к самому классу через атрибут
self.__class__
. Это
означает, что методы экземпляра также могут модифицировать состояние
класса.
Методы класса
Давайте сравним это со вторым методом,
MyClass.classmethod
. Я пометил
этот метод декоратором
@classmethod
1
, чтобы обозначить его как метод
класса.
Вместо параметра
self
методы класса принимают параметр
cls
, который
указывает на класс, а не на экземпляр объекта во время вызова этого ме-
тода.
1
См. документацию Python «@classmethod»:
https://docs .python .org/3/library/functions .
html#classmethod
144 Глава 4 • Классы и ООП
Поскольку метод класса имеет доступ только к этому аргументу
cls
, он
не может менять состояние экземпляра объекта. Для этого потребовался
бы доступ к параметру
self
. Однако методы класса по-прежнему могут
модифицировать состояние класса, которое применимо во всех экзем-
плярах класса.
Статические методы
Третий метод,
MyClass.staticmethod
, был помечен декоратором
@sta-
ticmethod
1
, чтобы обозначить его как статический метод.
Этот тип метода не принимает ни параметр
self
, ни параметр
cls
, хотя,
конечно же, он может быть сделан так, чтобы принимать произвольное
количество других параметров.
Как результат, статический метод не может модифицировать состояние
объекта или состояние класса. Статические методы ограничены теми
данными, к которым они могут получить доступ, — они, прежде всего,
являются средством организации пространства имен ваших методов.
Посмотрим на них в действии!
Я знаю, что пояснения были весьма теоретизированными до этого места.
Более того, полагаю, что важно на практике развить интуитивное пони-
мание того, как эти типы методов различаются. Именно поэтому теперь
мы пробежимся по нескольким примерам.
Взглянем на то, как эти методы себя ведут в действии, когда мы их вы-
зываем. Начнем с создания экземпляра класса, а затем вызовем три опре-
деленных в нем разных метода.
Класс
MyClass
был создан так, чтобы реализация каждого метода воз-
вращала кортеж, содержащий информацию, которую мы можем исполь-
зовать, чтобы проследить, что происходит и к каким частям класса или
объекта этот метод может получить доступ.
1
См. документацию Python «@staticmethod»:
https://docs .python .org/3/library/functions .
html#staticmethod
4 .8 . Срыв покровов с методов экземпляра 145
Вот что происходит, когда мы вызываем
Do'stlaringiz bilan baham: |