Привет. Настало время поговорить о дескрипторах. С помощью дескрипторов в Python реализована практически вся магия при работе с объектами, классами и методами. Чтобы определить свой собственный дескриптор, нужно определить класс. методы __get__, __set__ или __delete__. После этого мы можем создать какой-то новый класс и в атрибут этого класса записать объект типа дескриптор. Таким образом, наш атрибут будет являться дескриптором. Что это значит? У него будет переопределено поведение при доступе к атрибуту, при присваивании значений или при удалении. Метод __get__, как вы могли догадаться, определяет поведение при доступе к атрибуту. Метод __set__ будет переопределять какое-то поведение, если мы попытаемся в наш атрибут что-то присвоить, а метод __delete__ будет говорить о том, что будет происходить, если мы удалим наш атрибут. Мы создадим объект класса Class и посмотрим, что будет происходить при обращении к атрибуту. Если мы просто попытаемся вывести наш атрибут, у нас вызовется метод __get__. Если мы запишем в него какое-то значение, у нас вызывается метод __set__. А если мы его удаляем, вызывается метод __delete__. Таким образом, Python позволяет вам переопределять поведение при доступе к атрибуту. Это очень мощная концепция, мощный механизм, который позволяет вам незаметно от пользователя определять различные поведения в ваших классах. Например, мы можем определить дескриптор Value, который будет переопределять поведение при присваивании значения в него. Мы определим наш класс с атрибутом, который будет являться дескриптором, и при присваивании значений в дескриптор у нас будет происходить модифицированное поведение. То есть наш метод __set__ говорит о том, что, когда мы присваиваем значение в наш дескриптор, мы не просто сохраняем это значение, но мы как-то его препроцессим. В данном случае просто умножаем на десять. Таким образом, когда мы присваиваем десятку в наш атрибут, который является дескриптором, у нас, на самом деле, сохраняется сотня. __get__ и __set__. Этого уже достаточно для того, чтобы наш класс являлся дескриптором. Вы можете переопределить любой из трех методов, и класс уже будет являться дескриптором. Если у вас переопределен только метод __get__, то это non-data дескриптор, если __set__ или delete — то это data дескриптор. Это говорит о том, в каком порядке они будут искаться, вызываться при поиске атрибутов. Давайте, чтобы подробнее разобраться с тем, как работают дескрипторы, напишем, как всегда, свой дескриптор. И в данном случае это будет дескриптор, который будет записывать все значения, которые ему присваиваются, в файл. Мы можем представить, что это какая-то важная информация, которую всегда нужно сохранять. Можете это сохранять в данном случае в файл или, например, сохранять куда-нибудь на сервер, делать реплику. Давайте создадим наш класс ImportantValue и переопределим его методы __get__. Метод __get__ принимает obj и obj_type, то есть объект, с которым вызвал дескриптор его тип, и метод __set__, который принимает объект, и значение, которое нужно присвоить. Таким образом, если мы создадим класс с какой-то важной информацией, например, это может быть класс Account, и важная информация — это, например, amount это какое-то, например, денежное значение, которое нам нужно всегда сохранять. И мы можем сделать это дескриптором. В данном случае наш amount является дескриптором с переопределенным поведением. Однако пока ничего не происходит в этом переопределенном поведении. Давайте это исправим. Определим метод __init__, и наш дескриптор должен принимать какое-то значение, которые мы должны сохранить. В данном случае он принимает amount и, допустим, просто его сохраняет. Пусть у нас будет 100 каких-то единиц на счету. Если мы будем менять значение нашего атрибута, мы хотим все это логировать. Будем всегда логировать в один и тот же файл, просто записывать. Не забудем сохранить все-таки само значение. Отлично. Когда мы пытаемся к нему обратиться, нужно вернуть это значение из amount. Отлично. Мы создали наш дескриптор. Давайте посмотрим, что будет происходить, когда мы попытаемся присвоить значение в amount. Для этого нам нужно создать объект класса Account. Пусть это будет bobs_account. И у Боба на счету какое-то количество единиц. Таким образом, если мы попытаемся это количество единиц, количество каких-то денег изменить, то есть мы сделаем amount не 100, например, а 150, эти изменения должны залогироваться в файл. Давайте проверим, есть ли они там. Мы просто читаем наш файл и смотрим, что туда записалось. Отлично! В нашем файле 150, потому что, когда мы присваивали значения в наш дескриптор, мы не только эти значения запоминали, но и записывали в файл. Таким образом, если мы запишем, например, 200, у нас в файле будет 200. Мы можем не перезаписывать этот файл, а, например, дополнять его. Таким образом, у нас каждый раз, когда мы будем присваивать, файл будет изменяться. Мы можем логировать все записи в этот атрибут. Что ж, давайте продолжим. И на самом деле, несмотря на то, что вы пользовались функциями и методами уже довольно давно, на самом деле функции и методы реализованы с помощью дескрипторов. Чтобы понять, что это действительно так, можно попробовать обратиться к одному и тому же методу с помощью объекта и с помощью класса. И окажется, что, когда мы обращаемся к методу через точку от объекта, у нас возвращается bound method. То есть это метод, привязанный уже к какому-то объекту, в данном случае object. А если мы обращаемся к методу от Class, то у нас это unbound method. Это просто функция. Как вы видите, один и тот же метод возвращает разные объекты в зависимости от того, как к нему обращаются. Это и есть поведение дескриптора. Вам уже знаком декоратор property, который позволяет вам использовать функцию как атрибут класса. В данном случае мы можем определить property full_name, который на самом деле хоть и является функцией, которая возвращает строчку, используется потом так же, как и обычный атрибут, то есть без вызова скобочек. В данном случае у нас класс User, у нас first_name и last_name, и full_name возвращает, очевидно, полное имя. При вызове full_name от объекта у нас вызывается функция full_name. Однако если мы пытаемся обратиться к full_name от класса, у нас получится объект типа property. На самом деле, property реализовано с помощью дескрипторов, потому что разное поведение в зависимости от того, как у нас вызывается этот объект. И мы можем написать свой собственный класс property, который будет эмулировать поведение стандартного property. Для этого нам нужно сохранить функцию, которую property получает, потому что property — это декоратор, он получает функцию. И когда мы обращаемся к нашему объекту, если он вызван от класса, мы просто возвращаем самого себя, а если у нас вызван наш атрибут с объектом, то мы возвращаем соответствующий getter, вызываем функцию. Таким образом, мы можем определить класс и использовать как стандартный декоратор property, так и новый только что созданный. В двух видах можем просто его использовать как декоратор с помощью синтаксического сахара, можем использовать как вызов функции. И окажется, что они работают идентично, потому что на самом деле property реализован именно с помощью дескриптора. Точно так же реализованы StaticMethod и ClassMethod. И мы можем точно так же написать свою реализацию ClassMethod и StaticMethod. StaticMethod просто сохраняет функцию. И когда она вызывается, когда мы пытаемся получить соответствующий атрибут, мы просто ее возвращаем, потому что это статический метод, нам не нужно передавать туда ни self, ни class. А ClassMethod, когда мы вызываем нашу функцию от объекта, то есть наш obj_type равен нулю, равен None, то мы передаем соответствующий obj_type первым значением. Как видите, как и ожидается от ClassMethod. ClassMethod принимает первым значением class. Именно это и делает наша реализация ClassMethod. На самом деле с помощью дескрипторов в Python реализовано очень много чего, и, например, есть такая конструкция __slots__, которая работает тоже с помощью дескрипторов. __slots__ позволяет вам определить класс, у которого есть жестко заданный набор атрибутов. Как вы знаете, когда мы создаем класс, у класса создается соответствующий словарь, в который мы записываем атрибуты, которые добавляются в объект. Очень часто это бывает излишне. У вас может быть огромное количество, например, объектов, и вы не хотите создавать каждый раз для каждого объекта словарь. Для этого приходит на помощь конструкция __slots__, которая вам позволяет жестко задать количество элементов, которые ваш класс может содержать. В данном случае мы говорим, что у нас в нашем классе должен быть только атрибут anakin. Собственно, он при инициализации и создается. Если мы попытаемся добавить в наш класс, в наш объект какой-то еще один атрибут, у нас ничего не получится, потому что у нас нет, собственно, справочника, нет dict, в который мы это записываем. И __slots__ реализуется с помощью определения дескрипторов для каждого из атрибутов. Мы познакомились с вами с дескрипторами, узнали, как они на самом деле работают, и на самом деле с помощью дескрипторов реализована практически вся магия, вся работа с методами и функциями в Python. Увидимся в следующем видео.