Лутц М. - Изучаем Python (1077325), страница 122
Текст из файла (страница 122)
В более реалистичном примере мы могли бы вызывать метод с именем С!чева!зе, присоединенный как атрибут к классу Езр1оуее, — вызов этого метода был бы бессмысленным без указания служащего, которому дается надбавка к зарплате. Как мы увидим позднее, РуЫтоп передает методам подразумеваемый экземпляр в виде специального первого аргумента, в соответствии с соглашением именуемого зе11.
Мы также узнаем, что методы могут вызываться как через экземпляры (например, ЬоЬ. о!чепэ!ее()), так и через классы (например, Евр1оуее. О)чеза!Зе(ЬоЬ)), причем обе формы играют одну и ту же роль в наших сценариях. Чтобы увидеть, как методы принимают свои подразумеваемые экземпляры, нам необходимо рассмотреть примеры программного кода. Создание деревьев классов с!азв С2. № Создат~ обьекты классов (овалы) с!азз СЗ с!азв С1(С2, СЗ).... № Связанные с суперклассами № Создать объекты зкзеипляров Глряиоугольники), № связанные со своиии классами 11 = С1() 12 = С1() Здесь мы построили дерево объектов классов, выполнив три инструк- ции с1ззз, и два объекта экземпляров, вызвав класс С1 дважды, как ес- ли бы это была обычная функция.
Экземпляры помнят класс, из кото- рого они были созданы, а класс С1 помнит о своих суперклассах. С технической точки зрения в этом примере используется то, что называется множественным наследованием, которое означает, что некий класс имеет более одного суперкласса над собой в дереве классов. Несмотря на всю отвлеченность наших разговоров, тем не менее за кулисами всех этих идей стоит реальный программный код. Мы создаем деревья и объекты в них с помощью инструкций с1азз и вызовов классов, которые позднее мы рассмотрим более подробно.
В двух словах: ° Каждая инструкция с1азз создает новый объект класса. ° Каждый раз, когда вызывается класс, он создает новый объект экземпляра. ° Экземпляры автоматически связываются с классами, из которых они были созданы. ° Классы связаны со своими суперклассами, перечисленными в круглых скобках в заголовке инструкции с1азз, — порядок следования в списке определяет порядок расположения в дереве. Чтобы создать дерево, изображенное на рис. 22.1, например, мы могли бы использовать следующий программный код (здесь я опустил реализацию классов): ООП с высоты 30 000 футов Из-за особенностей поиска в дереве наследования имеет большое значение, к какому из объектов присоединяется тот или иной атрибут, — тем самым определяется его область видимости.
Атрибуты, присоединяемые к экземплярам, принадлежат только этим конкретным экземплярам, но атрибуты, присоединяемые к классам, совместно используются всеми подклассами и экземплярами. Позднее мы подробно изучим программный код, выполняющий присоединение атрибутов к этим объектам. Мы увидим, что: ° атрибуты обычно присоединяются к классам с помощью инструкций присваивания внутри инструкции с1з55, а не во вложенных инструкциях бе1, определяющих функции. ° атрибуты обычно присоединяются к экземплярам с помощью присваивания значений специальному аргументу с именем зе11, передаваемому функциям внутри классов. Например, классы определяют поведение своих экземпляров с помощью функций, создаваемых инструкциями бег внутри инструкции 51ззз.
Поскольку такие вложенные инструкции бет выполняют присваивание именам внутри класса, они присоединяются к объектам классов в виде атрибутов и будут унаследованы всеми экземплярами и подклассами: с1555 С1(С2, СЗ): ое( ветпазе(ее1(, ипо): ве1(.паза = иоо а Создать и связать класс С1 а присвоить, ст ветпаве а Бе) т - либо РС либо 12 11 = С!() 12 = С1() 11 ве(паве( ЬоЬ ) 12.5Е(Паев('вв!') Отти( 11.паве а Созвать два зкзеипляра а Записать 'Ьоо в Х!.паве а Записать 'ве1' в 12 паве Ф заведет 'Ьоо' Синтаксис инструкции бег в этом контексте — совершенно обычный. С функциональной точки зрения, когда инструкция бег появляется внутри инструкции с1а55, как в этом примере, она обычно называется ме.
птодом и автоматически принимает специальный первый аргумент с именем зе11, который содержит ссылку на обрабатываемый экземпляр.' Так как классы являются фабриками, способными производить множество экземпляров, их методы 'обычно используют этот, получаемый Если когда-нибудь вам приходилось использовать язык С++ влн дача, вы без труде поймете, что в языке РуЫтоп имя ве1( — вто то же, что указатель (юв, НО З ЯЗЫКЕ РуЫтоп аргумент 5Е11 всегда используется явно, чтобы сде- лать обращения к атрибутам более очевидными.
В языке РуЫ(оп, если в инструкции с1аее в круглых скобках перечис- лено более одного суперкласса (как в случае с классом С1 в данном при- мере), их порядок следования слева направо определяет порядок поис- ка атрибутов в суперклассах. Глава 22. ООП: общая картина автоматически, аргумент ве1( для получения или изменения значений атрибутов конкретного экземпляра, который обрабатывается мето- дом. В предыдущем фрагменте программного кода имя ве1( использу- ется для сохранения имени служащего в конкретном экземпляре. Подобно простым переменным, атрибуты классов и экземпляров не объявляются заранее, а появляются, когда им впервые выполняется присваивание значений. Когда метод присваивает значение атрибуту с помощью имени ве1(, он тем самым создает атрибут экземпляра, находящегося в нижнем уровне дерева классов (то есть в одном из прямоугольников), потому что имя ве1( автоматически ссылается на обрабатываемый экземпляр.
Фактически благодаря тому, что все объекты в дереве классов — это всего лишь объекты пространств имен, мы можем получать или устанавливать любой из их атрибутов, используя соответствующие имена. Например, выражение С1. ветпаае является таким же допустимым, как и 11. ве(паве, при условии, что имена с1 и 11 находятся в области видимости программного кода. В настоящий момент класс С1 не присоединяет атрибут паве к экземплярам, пока не будет вызван метод вегпапе.
Фактически попытка обратиться к имени 11.папе до вызова 11.ве(паве приведет к появлению ошибки, сообщающей о неопределенном имени. Если в классе потребуется гарантировать, что атрибут, такой как паве, всегда будет присутствовать в экземплярах, то такой атрибут должен создаваться на этапе создания класса, как показано ниже: о1авв С1(С2, СЗ). ое( (пы (ве1С ипо).
д создать имя пди создании класса ве1П паве = иле л Зе) т - либо РС либо 12 я Записать 'ЬоЬ' п 1) изме д Записать 'ме1' в 12 ламе д Вмведет 'ЬоЬ' 11 = С1( ЬоЬ') 12 = С1('ее1') Отто( 11 паоЕ В этом случае интерпретатор Ру1)топ автоматически будет вызывать метод с именем тпм каждый раз при создании экземпляра класса. Новый экземпляр будет передаваться методу гп! Ь в виде первого аргумента ве1(, а любые значения, перечисленные в круглых скобках при вызове класса, будут передаваться во втором и последующих за ним аргументах.
В результате инициализация экземпляров будет выполняться в момент их создания, без необходимости вызывать дополнительные методы. Метод тпш известен как конструктор„так как он запускается на этапе конструирования экземпляра. Этот метод является типичным представителем большого класса методов, которые называются метода.
ми перегрузки операторов. Более подробно эти методы будут рассматриваться в последующих главах. Такие методы наследуются в дереве классов как обычно, а их имена начинаются и заканчиваются двумя 571 ООП с высоты 30 000 футов символами подчеркивания, чтобы подчеркнуть их особенное назначение. Интерпретатор РуФЬоп вызывает их автоматически, когда экземпляры, поддерживающие их, участвуют в соответствующих операциях, и они главным образом являются альтернативой вызовам простых методов. Кроме того, они являются необязательными: при их отсутствии соответствующие операции экземплярами не поддерживаются. Например, чтобы реализовать пересечение множеств, класс может предусмотреть реализацию метода тщегзест или перегрузить оператор 6, описав логику его работы в методе с именем асс . Поскольку использование операторов делает экземпляры более похожими на встроенные типы, это позволяет определенным классам обеспечивать непротиворечивый и естественный интерфейс и быть совместимыми с программным кодом, который предполагает выполнение операций над объектами встроенных типов.
ООП вЂ” это многократное использование программного кода Вот, в основном, и все описание ООП в языке Ру$Ьоп (за исключением некоторых синтаксических особенностей). Конечно, в ООП присутствует не только наследование. Например, перегрузка операторов может применяться гораздо шире, чем описывалось до сих пор, — классы могут предоставлять собственные реализации таких операций, как доступ к элементам по их индексам, получение значений атрибутов, вывод и многие другие. Но вообще говоря, ООП реализует поиск атрибутов в деревьях. Тогда зачем нам погружаться в тонкости создания деревьев объектов и выполнения поиска в них7 Нужно накопить некоторый опыт, чтобы увидеть, как при грамотном использовании классы поддерживают возможность многократного использования программного кода способами, которые недоступны в других программных компонентах. Используя классы, мы программируем, настраивая написанное программное обеспечение, вместо того чтобы изменять существующий программный код или писать новый код в каждом новом проекте.