>>> c.diameter
10
А вот и самое интересное — мы можем изменить значение атрибута radius в любой момент и свойство diameter будет рассчитано на основе текущего значения атрибута radius:
>>> c.radius = 7
>>> c.diameter
14
Если вы не укажете сеттер для атрибута, то не сможете устанавливать его значение извне. Это удобно для атрибутов, которые должны быть доступны только для чтения:
>>> c.diameter = 20
Traceback (most recent call last):
··File "", line 1, in
AttributeError: can't set attribute
У использования свойств вместо непосредственного доступа к атрибутам имеется еще одно преимущество: если вы измените определение атрибута, вам нужно будет поправить только код внутри определения класса вместо того, чтобы править все вызовы.
Искажение имен для безопасности
В примере с классом Duck из предыдущего раздела мы вызывали наш (не полностью) скрытый атрибут hidden_name. Python предлагает соглашения по именованию для атрибутов, которые не должны быть видимы за пределами определения их классов: имена начинаются с двух нижних подчеркиваний (__).
Переименуем атрибут hidden_name в __name, как показано здесь:
>>> class Duck():
…·····def __init__(self, input_name):
…·········self.__name = input_name
…·····@property
…·····def name(self):
…·········print('inside the getter')
…·········return self.__name
…·····@name.setter
…·····def name(self, input_name):
…·········print('inside the setter')
…·········self.__name = input_name
…
Теперь проверим, работает ли все как полагается:
>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'
Выглядит хорошо. И вы не можете получить доступ к атрибуту __name:
>>> fowl.__name
Traceback (most recent call last):
··File "", line 1, in
AttributeError: 'Duck' object has no attribute '__name'
Это соглашение по именованию не делает атрибут закрытым, но Python искажает имя для того, чтобы внешний код не наткнулся на него. Если вам любопытно и вы никому не расскажете, я покажу вам, как будет выглядеть атрибут:
>>> fowl._Duck__name
'Donald'
Обратите внимание на то, что на экране не появилась надпись inside the getter. Хотя эта защита не идеальна, искаженное имя отказывается случайно или намеренно получать доступ к атрибуту.
Типы методов
Одни данные (атрибуты) и функции (методы) являются частью самого класса, а другие — частью объектов, которые созданы на его основе.
Когда вы видите начальный аргумент self в методах внутри определения класса, этот метод является методом экземпляра. Такие методы вы обычно пишете при создании собственного класса. Первый параметр метода экземпляра — это self, и Python передает объект методу, когда вы его вызываете.
В противоположность ему метод класса влияет на весь класс целиком. Любое изменение, которое происходит с классом, влияет на все его объекты. Внутри определения класса декоратор @classmethod показывает, что следующая функция является методом класса. Первым параметром метода также является сам класс. Согласно традиции этот параметр называется cls, поскольку слово class является зарезервированным и не может быть использовано здесь. Определим метод класса для А, который будет подсчитывать количество созданных объектов:
>>> class A():
…·····count = 0
…·····def __init__(self):
…·········A.count += 1
…·····def exclaim(self):
…·········print("I'm an A!")
…·····@classmethod
…·····def kids(cls):
…·········print("A has", cls.count, "little objects.")
…
>>>
>>> easy_a = A()
>>> breezy_a = A()
>>> wheezy_a = A()
>>> A.kids()
A has 3 little objects.
Обратите внимание на то, что мы вызвали метод A.count (атрибут класса) вместо self.count (который является атрибутом объекта). В методе kids() мы использовали вызов cls.count, но с тем же успехом могли бы применять вызов A.count.
Третий тип методов не влияет ни на классы, ни на объекты: он находится внутри класса только для удобства вместо того, чтобы располагаться где-то отдельно. Это статический метод, перед которым располагается декоратор @staticmethod, не имеющий в качестве начального параметра ни self, ни класс class. Рассмотрим пример, который служит в качестве рекламы класса CoyoteWeapon:
>>> class CoyoteWeapon():
…·····@staticmethod
…·····def commercial():
…·········print('This CoyoteWeapon has been brought to you by Acme')
…
>>>
>>> CoyoteWeapon.commercial()
This CoyoteWeapon has been brought to you by Acme
Обратите внимание на то, что нам не нужно создавать объект класса CoyoteWeapon, чтобы получить доступ к этому методу. Это здорово.
Утиная типизация
В Python имеется также реализация полиморфизма — это значит, что одна операция может быть произведена над разными объектами независимо от их класса.
Используем уже знакомый нам инициализатор __init__() для всех трех классов Quote, но добавим две новые функции:
• who() возвращает значение сохраненной строки person;
• says() возвращает сохраненную строку words, имеющую особую пунктуацию.
Посмотрим на них в действии:
>>> class Quote():
…·····def __init__(self, person, words):
…·········self.person = person
…·········self.words = words
…·····def who(self):
…·········return self.person
…·····def says(self):
…·········return self.words + '.'
…
>>> class QuestionQuote(Quote):
…······def says(self):
…··········return self.words + '?'
…
>>> class ExclamationQuote(Quote):
…······def says(self):
…··········return self.words + '!'
…
>>>
Мы не меняли способ инициализации классов QuestionQuote и ExclamationQuote, поэтому не перегружали их методы __init__(). Далее Python автоматически вызывает метод __init__() родительского класса Quote, чтобы сохранить переменные объекта person и words. Поэтому мы можем получить доступ к атрибуту self.words в объектах, созданных с помощью подклассов QuestionQuote и ExclamationQuote.
Далее создадим несколько объектов:
>>> hunter = Quote('Elmer Fudd', "I'm hunting wabbits")
>>> print(hunter.who(), 'says:', hunter.says())
Elmer Fudd says: I'm hunting wabbits.
>>> hunted1 = QuestionQuote('Bugs Bunny', "What's up, doc")
>>> print(hunted1.who(), 'says:', hunted1.says())
Bugs Bunny says: What's up, doc?
>>> hunted2 = ExclamationQuote('Daffy Duck', "It's rabbit season")
>>> print(hunted2.who(), 'says:', hunted2.says())
Daffy Duck says: It's rabbit season!
Три разные версии метода says() обеспечивают разное поведение трех классов. Так выглядит традиционный полиморфизм в объектно-ориентированных языках. Python пошел немного дальше и позволяет вам вызывать методы who() и says() для