Что такое дескриптор в python

Еще немного о дескрипторах в Python

Не так давно на Хабре уже был перевод статьи Раймонда Хеттингера Руководство к дескрипторам. В этой статье я постараюсь рассмотреть вопросы, которые возникли у меня после прочтения. Будет немного примеров кода, собственно вопросов и ответов к ним. Для понимания того, о чем речь, вам нужно знать, что такое дескрипторы и зачем они.

Когда вызываются дескрипторы?

Рассмотрим следующий код:

>>> class M(type):
. def __new__(cls, name, bases, dct):
. dct[‘test’] = lambda self, x: x*2
. return type.__new__(cls, name, bases, dct)
.
>>> class A(object):
. def __init__(self):
. self.__dict__[‘test2’] = lambda self, x: x*4
. __metaclass__ = M
.
>>> A().test(2)
4
>>> A().test2(2)
Traceback (most recent call last):
File » stdin > «, line 1, in module >
TypeError: lambda > () takes exactly 2 arguments (1 given)

Что не так? Вроде добавляем функцию одинаково, используя словарь объекта. Почему не вызывается дескриптор для функции «test2»? Дело в том, что функция «test» определяется для словаря класса, а функция «test2» — для словаря объекта:

>>> ‘test’ in A.__dict__
True
>>> ‘test2’ in A.__dict__
False
>>> ‘test’ in A().__dict__
False
>>> ‘test2’ in A().__dict__
True

Отсюда — первый ответ: функция «__get__» вызывается только для дескрипторов, которые являются свойствами класса, а не свойствами объектов этого класса.

Что является дескриптором?

Предыдущий ответ сразу вызывает вопрос — что значит «только для дескрипторов»? Я ведь не создавал никаких дескрипторов, я создал только функцию!

Ответ ожидаемий — функции в Питоне являются дескрипторами(если быть точнее — дескрипторами не данных):

Bound/Unbound методы

И напоследок самое вкусное. Задача:

Есть объект некоего класса, к которому необходимо динамически добавить метод, которому, конечно, должен передаваться «self» параметр. Не представляется возможным добавлять этот метод в словарь класса (мы не хотим повлиять на другие объекты).

Решить «в лоб» не выходит:

>>> class A(object):
. def __init__(self):
. self.x = 3
.
>>> def add_x(self, add):
. self.x += add
. print ‘Modified value: %s’ % (self.x,)
.
>>> a = A()
>>> a.add_x = add_x
>>> a.x
3
>>> a.add_x(3)
Traceback (most recent call last):
File » stdin > «, line 1, in module >
TypeError: add_x() takes exactly 2 arguments (1 given)

Получаем ожидаемую ошибку, которая подтверждает первый ответ — при обращении к свойству-дескриптору объекта не используется «__get__». Что же делать?

Поиграем немного с методом «__get__» функции, которую мы только что создали:

О, кажется это то, что нам надо! В предпоследней строчке мы получили «bound» метод — именно так смотрелся бы вызов «A().add_x», если в классе «A» был бы определен метод «add_x». А в последней строчке видим «ожидаемый» результат вызова «A.add_x». Теперь мы знаем, как добавить метод к объекту:

Бинго!
И так, именно метод «__get__» для функций-дескрипторов создает «bound/unbound» методы классов и объектов. И нет здесь никакой магии 🙂

Литература

Что еще почитать? На самом деле — немного. Есть несколько глав, в которых рассказывается о дескрипторах, в Python Data Model (implementing-descriptors), ну и можно снова вспомнить действительно хорошую статью Хеттингера (оригинал).

Источник

HOWTO по дескрипторам¶

Аннотация¶

Определяет дескрипторы, резюмирует протокол и показывает, как дескрипторы вызываются. Исследует пользовательский дескриптор и несколько встроенных дескрипторов Python, включая функции, свойства, статические методы и методы классов. Показывает, как работает каждый из них, предоставляя эквивалент на чистом Python и образец приложения.

Изучение дескрипторов не только предоставляет доступ к большему набору инструментов, но и даёт более глубокое понимание того, как работает Python, и понимание элегантности его дизайна.

Определение и введение¶

Дескрипторный протокол¶

Вот и всё. Определите любой из этих методов, и объект будет считаться дескриптором и может переопределить поведение по умолчанию при поиске в качестве атрибута.

Дескрипторы без данных и дескрипторы данных различаются по способу вычисления переопределений по отношению к записям в словаре экземпляра. Если в словаре экземпляра есть запись с тем же именем, что и дескриптор данных, дескриптор данных имеет приоритет. Если в словаре экземпляра есть запись с тем же именем, что и дескриптор без данных, запись словаря будет приоритетнее.

Вызов дескрипторов¶

Детали вызова зависят от того, является ли obj объектом или классом.

Важные моменты, о которых следует помнить:

Подробности реализации находятся в: c:func: super_getattro() в Objects/typeobject.c, а эквивалент на чистом Python можно найти в Учебнике Гвидо.

Пример дескриптора¶

Следующий код создаёт класс, объекты которого являются дескрипторами данных, которые печатают сообщение при каждом получения или установки. Переопределение __getattribute__() — это альтернативный подход, который может сделать это для каждого атрибута. Однако этот дескриптор полезен для мониторинга только нескольких выбранных атрибутов:

Протокол прост и предлагает захватывающие возможности. Некоторые варианты использования настолько распространены, что были объединены в отдельные вызовы функций. Свойства, связанные методы, статические методы и методы классов основаны на протоколе дескриптора.

Свойства¶

Вызов property() — это краткий способ создания дескриптора данных, который запускает вызовы функций при доступе к атрибуту. Его сигнатура:

В документации показано типичное использование для определения управляемого атрибута x :

Чтобы увидеть, как property() реализован в терминах протокола дескриптора, в виде его чистого эквивалента Python:

Встроенная функция property() помогает всякий раз, когда пользовательский интерфейс предоставляет доступ к атрибутам, а затем последующие изменения требуют вмешательства метода.

Функции и методы¶

Объектно-ориентированные функции Python построены на среде, основанной на функциях. Используя дескрипторы без данных, два подхода легко объединяются.

Для поддержки вызовов методов функции включают метод __get__() для привязки методов во время доступа к атрибутам. Это означает, что все функции не являются дескрипторами данных, которые возвращают связанные методы, когда они вызываются из объекта. В чистом Python это работает так:

Запуск интерпретатора показывает, как дескриптор функции работает на практике:

Статические методы и методы классов¶

Дескрипторы без данных предоставляют простой механизм для вариаций обычных шаблонов связывания функций с методами.

В этой таблице показаны два наиболее полезных варианта привязки:

ПреобразованиеВызывается из объектаВызывается из класса
functionf(obj, *args)f(*args)
staticmethodf(*args)f(*args)
classmethodf(type(obj), *args)f(klass, *args)

Поскольку статические методы возвращают базовую функцию без изменений, вызовы примеров неинтересны:

При использовании протокола дескриптора без данных чистая версия Python staticmethod() будет выглядеть так:

В отличие от статических методов, методы класса добавляют ссылку на класс к списку аргументов перед вызовом функции. Этот формат одинаков для того, является ли вызывающий объект объектом или классом:

Это поведение полезно, когда у функции должна быть только ссылка на класс и не заботится о каких-либо базовых данных. Одно из применений методов классов — создание альтернативных конструкторов классов. В Python 2.3 метод классов dict.fromkeys() создаёт новый словарь из списка ключей. Эквивалент на чистом Python:

Теперь можно построить новый словарь уникальных ключей:

При использовании протокола дескриптора без данных чистая версия Python classmethod() будет выглядеть так:

Источник

Атрибуты и протокол дескриптора в Python

Рассмотрим такой код:

Сегодня мы разберём ответ на вопрос: «Что именно происходит, когда мы пишем foo.bar

Вы, возможно, уже знаете, что у большинства объектов есть внутренний словарь __dict__, содержащий все их аттрибуты. И что особенно радует, как легко можно изучать такие низкоуровневые детали в Питоне:

Давайте начнём с попытки сформулировать такую (неполную) гипотезу:

Пока звучит похоже на правду:

Что такое дескриптор в python. Смотреть фото Что такое дескриптор в python. Смотреть картинку Что такое дескриптор в python. Картинка про Что такое дескриптор в python. Фото Что такое дескриптор в python

Теперь предположим, что вы уже в курсе, что в классах можно объявлять динамические аттрибуты:

Хм… ну ладно. Видно что __getattr__ может эмулировать доступ к «ненастоящим» атрибутам, но не будет работать, если уже есть объявленная переменная (такая, как foo.bar, возвращающая ‘hello!’, а не ‘goodbye!’). Похоже, всё немного сложнее, чем казалось вначале.

И действительно: существует магический метод, который вызывается всякий раз, когда мы пытаемся получить атрибут, но, как продемонстрировал пример выше, это не __getattr__. Вызываемый метод называется __getattribute__, и мы попробуем понять, как в точности он работает, наблюдая различные ситуации.

Пока что модифицируем нашу гипотезу так:

foo.bar эквивалентно foo.__getattribute__(‘bar’), что примерно работает так:

Проверим практикой, реализовав этот метод (под другим именем) и вызывая его напрямую:

Выглядит корректно, верно?

Что такое дескриптор в python. Смотреть фото Что такое дескриптор в python. Смотреть картинку Что такое дескриптор в python. Картинка про Что такое дескриптор в python. Фото Что такое дескриптор в python

Отлично, осталось лишь проверить, что поддерживается присвоение переменных, после чего можно расходиться по дом… —

my_getattribute возвращает некий объект. Мы можем изменить его, если он мутабелен, но мы не можем заменить его на другой с помощью оператора присвоения. Что же делать? Ведь если foo.baz это эквивалент вызова функции, как мы можем присвоить новое значение атрибуту в принципе?

Когда мы смотрим на выражение типа foo.bar = 1, происходит что-то больше, чем просто вызов функции для получения значения foo.bar. Похоже, что присвоение значения атрибуту фундаментально отличается от получения значения атрибута. И правда: мы может реализовать __setattr__, чтобы убедиться в этом:

Пара вещей на заметку относительно этого кода:

Что такое дескриптор в python. Смотреть фото Что такое дескриптор в python. Смотреть картинку Что такое дескриптор в python. Картинка про Что такое дескриптор в python. Фото Что такое дескриптор в python

А ведь у нас есть ещё и property (и его друзья). Декоратор, который позволяет методам выступать в роли атрибутов.

Давайте постараемся понять, как это происходит.

Просто ради интереса, а что у нас в f.__dict__?

В __dict__ нет ключа bar, но __getattr__ почему-то не вызывается. WAT?

bar — метод, да ещё и принимающий в качестве параметра self, вот только это метод находится в классе, а не в экземпляре класса. И в этом легко убедиться:

Ключ bar действительно находится в словаре атрибутов класса. Чтобы понять работу __getattribute__, нам нужно ответить на вопрос: чей __getattribute__ вызывается раньше — класса или экземпляра?

Видно, что первым делом проверка идёт в __dict__ класса, т.е. у него приоритет перед экземпляром.

Что такое дескриптор в python. Смотреть фото Что такое дескриптор в python. Смотреть картинку Что такое дескриптор в python. Картинка про Что такое дескриптор в python. Фото Что такое дескриптор в python

Погодите-ка, а когда мы вызывали метод bar? Я имею в виду, что наш псевдокод для __getattribute__ никогда не вызывает объект. Что же происходит?

Вся суть тут. Реализуйте любой из этих трёх методов, чтобы объект стал дескриптором и мог менять дефолтное поведение, когда с ним работают как с атрибутом.

Если объект объявляет и __get__(), и __set__(), то его называют дескриптором данных («data descriptors»). Дескрипторы реализующие лишь __get__() называются дескрипторами без данных («non-data descriptors»).

Оба вида дескрипторов отличаются тем, как происходит перезапись элементов словаря атрибутов объекта. Если словарь содержит ключ с тем же именем, что и у дескриптора данных, то дескриптор данных имеет приоритет (т.е. вызывается __set__()). Если словарь содержит ключ с тем же именем, что у дескриптора без данных, то приоритет имеет словарь (т.е. перезаписывается элемент словаря).

Чтобы создать дескриптор данных доступный только для чтения, объявите и __get__(), и __set__(), где __set__() кидает AttributeError при вызове. Реализации такого __set__() достаточно для создания дескриптора данных.

Короче говоря, если вы объявили любой из этих методов — __get__, __set__ или __delete__, вы реализовали поддержку протокола дескриптора. А это именно то, чем занимается декоратор property: он объявляет доступный только для чтения дескриптор, который будет вызываться в __getattribute__.

Последнее изменение нашей реализации:

foo.bar эквивалентно foo.__getattribute__(‘bar’), что примерно работает так:

Попробуем продемонстрировать на практике:

Что такое дескриптор в python. Смотреть фото Что такое дескриптор в python. Смотреть картинку Что такое дескриптор в python. Картинка про Что такое дескриптор в python. Фото Что такое дескриптор в python

Мы лишь немного поскребли поверхность реализации атрибутов в Python. Хотя наша последняя попытка эмулировать foo.bar в целом корректна, учтите, что всегда могут найтись небольшие детали, реализованные по-другому.

Надеюсь, что помимо знаний о том, как работают атрибуты, мне так же удалось передать красоту языка, который поощряет вас к экспериментам. Погасите часть долга знаний сегодня.

Источник

ООП. Дескрипторы

В этой лекции мы рассмотрим такой важный механизм как дескрипторы, а также разберемся с тем как же устроены методы класса.

Свойства

Перед тем как говорить о дескрипторах давайте еще раз поговорим о свойствах (property). Рассмотрим следующий пример: пусть у нас есть класс «Профиль пользователя», который включает следующие поля: имя, фамилия и дата рождения.

Из примера видно, что, во-первых, возраст пользователя вычисляется при каждом обращении, во-вторых, мы только получаем значение и никогда его не изменяем. Было бы логично, чтобы клиентский код работал с возрастом как с обычным атрибутом (свойством) доступным только для чтения и python предоставляет нам для этого механизм свойств (propertes):

Таким образом, свойства дают нам возможность создавать, аналогично другим языкам программирования (например, Java), сеттеры и геттеры, а также вычисляемые свойства (computed properties):

Чтобы понять как работают свойства необходимо разобраться с дескрипторами.

Дескрипторы

В документации дано следующее определение дескрипторов:

Дескрипторы, которые реализуют только __get__ называются дескрипторами не данных (non-data descriptors), а дескрипторы, которые реализуют __set__ и/или __delete__ называются дескрипторами данных (data descriptors). Рассмотрим следующий пример:

Из примера видно, что при обращении к d1 автоматически был вызван метод __get__ определенный на дескрипторе:

Поэтому теперь должно быть понятно, почему при обращении к d2 мы получили просто экземпляр класса. Порядка разрешения имен атрибутов и методов мы коснемся в следующих лекциях.

В Python дескрипторы используются достаточно часто, в том числе и в самом языке, например, функции это дескрипторы:

Это позволяет автоматически передавать экземпляр класса в качестве первого аргумента ( self ), давайте посмотрим на вызов func_descr_get :

Если obj не был передан, то мы имеем дело с обычной функцией, в противном случае это метод и мы «биндим» объект в качестве первого аргумента. На python реализацию функций можно было бы записать так:

А вот примеры реализаций декораторов @staticmethod и @classmethod:

И наконец реализация @property:

Пример простой ORM можно найти в репозитории с лекциями.

Источник

Русские Блоги

Обучение дескрипторов в Python

Что такое дескриптор

ДескрипторЭто одна из ключевых особенностей классов Python в новом стиле. Он предоставляет мощные API-интерфейсы для атрибутов объектов. Вы можете подумать оДескрипторАгент, представляющий атрибуты объекта. Когда вам нужен атрибут, вы можете получить к нему доступ через дескриптор в соответствии с ситуацией, с которой вы столкнулись (взято из программирования ядра Python).

Пример анализа

Используйте методы класса для создания дескрипторов

Которые только достигли __set__() Метод рассматривается как дескриптор метода или дескриптор, не связанный с данными. Достигнутые одновременно __set__() с участием __get__() Класс методов называется дескриптором данных.

Сначала определите класс дескриптора данных

Определим класс, который вызывает дескриптор данных

Обнаружено, что при доступе к атрибуту desc Myclass дескриптор __get__() метод. Этим достигается роль дескрипторов (доступ к свойствам объекта можно изменять).

Принцип вызова: для дескрипторов атрибутов класса, если синтаксический анализатор обнаруживает, что атрибут x является дескриптором, он будет проходить внутреннюю type.__getattribute__() (Вызывается безоговорочно при доступе к атрибутам, вызывается первым), он может Class.x Перевести в Class.__dict__[‘x’].__get__(None, Class) Приходите

Дескриптор определяется как атрибут класса выше. Каковы сходства и различия, когда мы определяем его как атрибут объекта?

Называется не так, как мы ожидали __get__() Method, просто скажите, что он является объектом Descriptor.

Так каков же эффект, если определение объекта дескриптора атрибута класса и имена атрибутов экземпляра одинаковы?

Результаты приведены ниже:

Видно, что к дескриптору обращаются во время инициализации. __set__() Метод, доступ к дескриптору __get__() метод. Так зачем снова вызывать метод дескриптора?
Чтобы объяснить эту проблему, давайте сначала поговорим о python Приоритет доступа к собственности в следующем:

Затем мы распечатываем список атрибутов вышеуказанного класса кода и экземпляра:

Результаты приведены ниже:

Согласно теории приоритета доступа к атрибутам, описанной выше, дескриптор данных> атрибут экземпляра. Когда python обнаруживает, что существует объект с тем же именем, что и определенный дескриптор в словаре объекта экземпляра, дескриптор имеет приоритет, а атрибут экземпляра будет перезаписан. Python перепишет поведение по умолчанию и вместо этого вызовет метод дескриптора.

Видно, что доступ к свойствам экземпляра в этом случае не вызывает дескриптор __get__() метод. Вместо этого он вызывает свои собственные свойства. Видно, что теория верна.

Создать дескриптор с использованием типа атрибута

property Встроенная функция имеет четыре параметра: property(fget=None, fset=None, fdel=None, doc=None) 。

fget: метод приобретения собственности
fset: метод установки свойства
fdel: метод удаления атрибута
doc: описание документа

Взглянем на реализацию:

Результаты приведены ниже:

Реализация дескриптора таким образом, хотя и проста. Но если атрибутов много, код будет раздутым.

Используйте модификаторы атрибутов для создания дескрипторов

Результаты приведены ниже:

Видите, код работает так же, как и раньше. Конкретный принцип повторяться не будет. Можно распечатать PropertyDesc Класс и экземпляр pro Подумайте о списке атрибутов.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *