Саммерфилд - Программирование на Python 3 (1077331), страница 97
Текст из файла (страница 97)
Затем, когда интерпретатор выполняет поиск атрибута паве аз хв1 или безсг1р11оп аз хв1, он вызывает метод де1 () дескриптора. Аргумент зе11 — это экземпляр дескриптора, аргумент !пз1апсе — это экземпляр класса Ргобьс1 (то есть значение ссылки эе!1 экземпляра класса Ргобас1), а аргумент онпе г — это класс владельца (в данном случае — класс Ргобьст). Для получения значения соответствующего атрибута экземпляра класса Ргобьст используется функция де1а11г() (в данном случае — значение соответствующего свойства), которая возвращает его экранированную версию.
В случае, когда в программе только для малой части всех строк необходимо предоставить экранированные версии строк, но эти строки очень длинные, а обращения к ним следуют достаточно часто, можно было бы предусмотреть использование кэша. Например: с1асз Саспебхв18пабон: бег !п!1 (эе11, а11г!ьи1е паве): эе1(.аттг!ЬЬ1е паве = а11г!Ьиге паве эе1(.сасПе = () беГ де1 (эе11, !пэгапсе, ьнпЕГ=Нэпе): хв1 1ех1 = эе1г.саспе.де1(гб(!пегапсе)) !Г хв1 тех! ш пот Иопе: ге1игп хв1 1ех1 ГЕтигп зЕ11.
саСПЕ. ЭЕ1ЬЕГап11(гб(гПЭ1апСЕ), хв!. эах. эахи1!1э. еэсаре( детатгг(! Ьэгапсе, эе11. а11г(ьь1е паве) ) ) Здесь в качестве ключа используется уникальный числовой идентификатор, а не сам экземпляр, потому что ключи словаря должны быть хешируемыми (каковыми и являются числовые идентификаторы), но нам не хотелось бы накладывать такое ограничение на классы, использующие дескриптор СасяебХв18Пабон. Ключи необходимы, потому что дескрипторы создаются для всего класса, а не для его экземпляров. (Метод б!с1, зетбе(аь!1() возвращает значение для заданного ключа или, если элемента с таким ключом нет, создает новый элемент с заданным ключом и значением и возвращает значение, что весьма удобно для нас.) Получив представление о том, как могут использоваться дескрипторы для генерирования данных без необходимости сохранять их, перейдем теперь к рассмотрению дескриптора, который может использоваться для сохранения всех атрибутов данных объекта, сняв с объекта необходимость что-либо сохранять.
В следующем примере мы будем использовать словарь, но в более жизненной ситуации данные можно было бы сохранять в файле или в базе данных. Ниже приводится начало Улучшенные приемы объектно-ориентированного программирования определения класса Ротпт, использующего дескриптор (из файла Ехтегпа(Б!огауе ру). 01555 Р01пт ,' 51015 = () х = Ехтегпа18тогаде("х") у = Ехтегпа13тог59е("у") оег 1птт (ее1г, х=о, у=о): ее1(.х = х зе1(.у = у Определив пустой кортеж в качестве значения атрибута 51отэ, мы тем самым гарантируем, что класс вообще не будет иметь никаких атрибутов данных. При попытке выполнить присваивание атрибуту зе1(.х интерпретатор обнаружит наличие дескриптора с именем «х» и вызовет его метод вес (). Остальная часть определения класса здесь не показана, но она полностью повторяет определение класса Ротпт из главы б.
Ниже приводится полное определение класса дескриптора Ехтегпа18тогаде: с1555 Ехтегпа18тогаде: 51отэ = ("аттг!поте паве",) этог59е = () ОЕГ 1П!т (5Е1г, емгтооте паве): ее1(.аттыьоте паве = аттгтьоте паве оег эет (ее1(, !пзтапсе, уа1ое): зе1(. 5тогпде[10(!пэтепсе), зе1(.еттгтьоте паве) = у51ие ОЕГ дЕт (5Е1(, !ПжаПСЕ, ОВПЕГвВОПЕ): 1( 1пэтепсе 15 попе: гетигп зе1Г ге!игл ее1г. 5тогаде[!б(!05тапсе), зе1г.еттг!ьоте паве) Каждый объект класса Ехтегпа18тогаде имеет единственный атрибут данных, атт г!Ьоте паве, который хранит имя атрибута данных класса- владельца.
Всякий раз, когда выполняется присваивание значения атрибуту, оно сохраняется в частном словаре класса это гаде. Точно так же, когда производится попытка прочитать значение атрибута, оно извлекается из словаря это гаде. Как и в любых других методах дескриптора, аргумент зе1! ссылается на экземпляр дескриптора, а 1пзтапсе — это ссылка зе1т для объекта, содержащего дескриптор, то есть здесь зе1( ссылается на объект класса Ехтегпа18тогзде, а 1пзтзпсе — на объект класса Ротпт. Несмотря на то, что атрибут зтогаде является атрибутом класса, тем не менее к нему можно обращаться следующим образом: зе11.
зтогаде (точно так же, как можно обращаться к некоторому методу класса эе1г.ее!Лог!()), потому что интерпретатор, не обнаружив его среди 436 Глава 8. Усовершенствованные приемы программирования атрибутов экземпляра, найдет его среди атрибутов класса. Единственный недостаток такого подхода состоит в том, что если экземпляр будет иметь атрибут с именем, совпадающим с именем атрибута класса, при попытке обратиться к этому имени всегда будет использоваться атрибут экземпляра. (Если это действительно необходимо, к атрибуту класса всегда можно обратиться, квалифицировав его именем класса, то есть Ех(егпа18тогаце.
зтогацв. Хотя такое жесткое определение в общем случае может отрицательно сказаться при создании подклассов, к частным атрибутам это не относится, так как механизм интерпретатора приведения имен все равно включает имя класса в имена таких атрибутов.) Здесь используется немного более сложная, чем прежде, реализация специального метода цет (), потому что мы предусмотрели возможность обращения объекта Ехтегпа18тогаце к самому себе. Например, представим, чтоу нас имеется экземпляр р = Ро!пт(З, 4), вэтомслучае доступ к координате х можно получить, обратившись к атрибуту р. х, а доступ к объекту Ехтегпа13тогаце, хранящему все координаты х, обратившись к Ротс(, х.
В завершение обсуждения дескрипторов создадим дескриптор Ргорегту, имитирующий поведение встроенной функции ргорегту() по крайней мере в отношении реализации методов доступа. Полный программный код находится в файле Ргорегс у.ру. Ниже приводится полное определение класса йааеАпоЕхтепз1оп, использующего этот дескриптор: с1азз МаееАпОЕхтепз1оп: Оет тптт (зе1Г, паве. ехтепзтоп): зе1Г. паве = паве зе1(.ехтепзтоп = ехтепзтоп ЭРгорегту я Задействуется нестандартный дескриптор Ргорегту Оет паве(зе1Г): гетыгп зе1Г.
паве ЭРгорегту я Задействуется нестандартный дескриптор Ргорегту Оет ехтепз(оп(зе!Г): гетогп зе11. ехтепз1оп юехтепзтоп.зеттег я Задействуется нестандартный дескриптор Ргорегту оег ехтепзаап(зе!т, ехтепюоп): зе!Г. ехтепзтоп = ехтепзтоп Порядок использования дескриптора точно такой же, как и в случае использования встроенных декораторов вргорегту и вргорегтуя(аяв. весте г. Ниже приводится начало определения дескриптора Р горе г1 у: с1азз Ргорегту оет тп11 (зе1г, деттег, зеттег=яопе): зе1(, цеыег = цеттег Улучшенные приемы объектно-ориентированного программирования 437 ве11, вяттег = веттяг ва11. паяя = деттег.
папе Метод инициализации класса принимает одну или две функции в качестве аргументов. Если он используется как декоратор, он просто получит декорируемую функцию, которая станет функцией чтения, а в качестве функции записи будет установлено значение йопе. В качестве имени свойства здесь используется имя метода чтения.
Для каждого свойства„для которого уже определена функция чтения, имеется возможность определить функцию записи, используя имя свойства. оег дет (вя1г, 1пвтапся, отгпег=йопе): 1( !пвтапсе !в йопе: гатнгп ве11 гвтнгп ве1г, даттег(!пвтапсе) Когда выполняется обращение к свойству, возвращается результат вызова функции чтения, которой в первом аргументе передается экземпляр класса. На первый взгляд запись ве1т.
деттег() напоминает вызов метода, но в действительности это не так. На самом деле яе1г. дегте г — это атрибут, который содержит ссылку на заданный метод. Поэтому фактически сначала происходит извлечение значения атрибута (ве11. деттег), а затем это значение вызывается как функция ( ). А так как атрибут вызывается как функция, а не как метод, мы должны явно передать ей соответствующий объект ве1т. Внутри методов дескриптора ссылка на сам объект (экземпляр класса, использующего дескриптор) называется 1пвтапсе (так как ве1( — это объект дескриптора). То же относится и к методу вет ().
ояг вот (ве11, !патассе, на1ня): Ы ве1(. вямег 1в йопв: га!ва АттгтЬнтаЕггог("'(О)' тв гааз-оп1у".Гогеат( ва1(, паве )) гятнгп ве11. веттег(!па!апов, на1не) В случае отсутствия функции записи возбуждается исключение Аттг!- ЬнтеЕггог; в противном случае функция вызывается н ей передаются ссылка на экземпляр класса и новое значение атрибута. ояг вяыег(ве11, ваттяг): ве1(. ваыаг = вяттвг гетнгп яе11, веттаг Этот метод вызывается, когда интерпретатор достигает, например, вызова Фехтев! оп. ветте г, с декорируемой функцией в качестве аргумента.
Он сохраняет указанный метод записи (который теперь может вызываться методом вет ()) и возвращает функцию записи, потому что любой декоратор должен возвращать декорированную им версию функции или метода. Мы рассмотрели три совершенно разные области использования дескрипторов. Дескрипторы представляют собой очень гибкое и мощное 438 Глава 8. Усовершенствованные приемы программирования средство, позволяющее выполнять за кулисами самые разные действия и выглядеть при этом простыми атрибутами клиентского класса (класса владельца).
Декораторы классов Точно так же, как имеется возможность создавать декораторы для функций и методов, можно создавать декораторы для целых классов. Декораторы классов принимают класс (результат действия инструкции с1аэа) и должны возвращать класс — обычно модифицированную версию декорируемого класса. В этом подразделе мы познакомимся с двумя декораторами классов и рассмотрим их реализацию. о В главе б мы создали собственный тип коллекции ЗогтКласс апгтеас!ат ейс!эт, который содержит обычный список в виде частстр.