Введение в дескрипторы в ООП в Python
Вы уже пользовались декоратором property,
а также staticmethod и classmethod.
Снаружи класса такие имена ведут себя
особым образом: свойство читают без
круглых скобок, а статический метод
можно вызвать через класс.
В Python за этим стоит один механизм - дескриптор. Это объект, который лежит в теле класса и перехватывает обращение к имени: чтение, запись или удаление.
Дескриптором считается любой объект
с методами __get__, __set__
или __delete__. Их не нужно
объявлять все сразу: достаточно одного,
чтобы имя в классе работало через
дескриптор.
Когда вы читаете свойство объекта,
Python сначала ищет имя в классе.
Если там лежит дескриптор, вызывается
его метод __get__. Запись в
свойство приведет к __set__,
а удаление - к __delete__.
Встроенный property - это готовый
дескриптор: он связывает геттер, сеттер
и deleter в одном объекте. То же самое
staticmethod и classmethod:
они оборачивают функцию и сами
реализуют __get__, чтобы метод
можно было вызвать через класс или
через объект так, как задумано.
Давайте сделаем свой простой дескриптор
только для чтения. У него будет метод
__get__ с тремя параметрами:
self, obj и owner.
Параметр obj - это объект, через
который обращаются к свойству.
Параметр owner - класс, в теле
которого объявлен дескриптор. Если
обращение идет через класс, а не
через объект, obj будет равен
None. В этом случае обычно
возвращают сам дескриптор.
Если obj не None, дескриптор
должен вернуть значение свойства для
этого объекта. Пока сохраним данные
в словаре экземпляра под ключом
'name':
class NameReader:
def __get__(self, obj, owner):
if obj is None:
return self
return obj.__dict__['name']
class User:
name = NameReader()
def __init__(self, name):
self.__dict__['name'] = name
Создадим объект и прочитаем имя как обычное свойство:
user = User('john')
print(user.name) # выведет 'john'
Обратимся к имени через класс
User:
print(User.name) # объект NameReader
Здесь сработало правило obj is None:
мы получили сам дескриптор, а не строку.
Так Python позволяет обращаться к
дескриптору с уровня класса, не создавая
объект.
Свой класс NameReader повторяет
идею property, но в явном виде:
логика чтения лежит в __get__.
Методы __set__ и __delete__
устроены так же: Python вызывает их
при записи и при удалении свойства.