Лутц М. - Изучаем Python (1077325), страница 127
Текст из файла (страница 127)
Обычная практика показывает, что перегруженные операторы должны работать так же, как и встроенные реализации. В данном случае это означает, что метод вс1 должен был бы возвращать новый объект в виде результата, а не изменять существующий экземпляр (эе1т); для непосредственного изменения объекта лучше предусмотреть метод во1, чем выполнять перегрузку оператора *(нвпример, воПЗ), вместо а 3).
590 Глава 23. Основы программирования классов После того как атрибуты будут созданы, мы можем обращаться к ним с помощью обычного синтаксиса. Когда класс используется таким способом, он напоминает структуры в языке С или записи в языке Рааса!— объект с присоединенными к нему полями (то же самое можно проделывать с ключами словарей, но для этого потребуется вводить дополнительные символы): »> ргьпт гес.паве ВоЬ № Квк структура в языке С или зались Обратите внимание: такой подход будет работать даже в случае, когда еще не было создано ни одного экземпляра класса. Классы — это полноценные объекты, даже если нет ни одного экземпляра.
Фактически они всего лишь самостоятельные пространства имен, поэтому, пока у нас имеется ссылка на класс, мы можем в любое время добавлять или изменять его атрибуты по своему усмотрению. Однако посмотрим, что произойдет, когда будут созданы два экземпляра класса: »> х = гес() »> у = гес() № Экземпляры наследуют имена из класса Эти экземпляры начинают свое существование как объекты абсолют- но пустых пространств имен. Однако из-за того, что они помнят класс, из которого были созданы, они по наследству получат атрибуты, кото- рые мы присоединили к классу: № Сейчас имена хранятся тол~ко в классе »> х.паае, у.паве ( ВоЬ', Воо') В действительности у этих экземпляров нет своих собственных атрибутов — они просто получают атрибут паве из класса. Тем не менее если выполнить присваивание атрибуту экземпляра, будет создан (илн изменен) атрибут этого объекта, а не другого — атрибуты обнаруживаются в результате поиска по дереву наследования, но операция присваивания значения атрибуту воздействует только на тот объект, к которому эта операция применяется.
Ниже экземпляр х получает свой собственный атрибут паве, а экземпляр у по-прежнему наследует атрибут паве, присоединенный к классу выше его: »> х.паве = 'Все' № Но лрисвамванив изивнит только объект х »> гес.паве, х.паве, у,паве ('ВоЬ', Все'. ВоЬ') »> гес. сасг .Кеув() ('аре', ' аобо)е ', ' Сос ', 'папе'] Фактически, как будет более подробно рассказано в следующей главе, атрибуты объекта пространства имен обычно реализованы в виде словарей, и деревья наследования классов (вообще говоря) тоже всего лишь словари со ссылками на другие словари. Если знать, куда смотреть, в этом можно убедиться явно. В большинстве объектов, созданных на базе классов, имеется атрибут бтсг, который является словарем пространства имен: 591 Самый простой е мире класс на языке Ру1йоп >» х, 61ст .Хауз() ('папе') »> у.
61ст .хауз() Здесь в словаре класса присутствуют атрибуты паве и зйе, которые мы создали ранее, объект х имеет свой собственный атрибут паве, а объект у по-прежнему пуст. Каждый экземпляр имеет ссылку на свой наследуемый класс, она называется 01звв, если вам захочется проверить ее: »> х.
с1авв <01авв пазп .гвс аг Ох006АЕЕ60> Классы также имеют атрибут Ьзве, который представляет собой кортеж его суперклассов — эти два атрибута описывают, как деревья классов размещаются в памяти. Главное, что следует из этого взгляда на внутреннее устройство, это то, что модель классов в языке РуФЬоп чрезвычайно динамична. Классы и экземпляры — зто всего лишь объекты пространств имен с атрибутами, создаваемыми на лету с помощью операции присваивания. Обычно эти операции присваивания выполняются внутри инструкции 01звв, но они могут находиться в любом другом месте, где имеется ссылка на один из объектов в дереве. Даже методы, которые обычно создаются инструкциями бе(, вложенными в инструкцию с1авв, могут создаваться совершенно независимо от объекта класса.
Например, ниже определяется простая функция вне какого-либо класса, которая принимает единственный аргумент: »> бас вррвгйава(ве1№): гесвгп ве)г.пава.вррег() № Аргуненг ве) г по-ооеинену необходим Здесь еще ничего не говорится о классе — это простая функция, и она может вызываться как обычная функция при условии, что объект, получаемый ею, имеет атрибут папе (в данном случае имя аргумента ве1( не имеет никакого особого смысла). Однако если зту простую функцию присвоить атрибуту нашего класса, она станет методом, вызываемым из любого экземпляра (а также через имя самого класса при условии, что функции вручную будет передан экземпляр) Фактически это одна из причин, почему в языке Рув)зоп аргумент вв! Г всегда должен явно объявляться в методах, — потому что методы могут создаваться как простые функции, независимо от классов, в им необходим явный аргумент со ссылкой на подразумеваемый экземпляр.
В противном случае интерпретатор ве смог бы обеспечить превращение простой функции з метод класса. Однако основная причина, по которой аргумент ве11 объявляется явно, заключается в том, чтобы сделать назначение имен более очевидным. Имена, к которым обращаются не через аргумент ве1Г, являются простыми переменными, тогда как имена, обращение к которым происходят через аргумент вв1(, очевидно являются атрибутами экземпляра. 592 Глава 23. Основы программирования классов »> гео.еегяоб = оррегйаае »> х.аеглоо() '80Е' »> у.аегаоо() '80в' № Вмзввтн метод для обработки к № То ме самое, но в зе1Г лервдвется у »> гео.ееглоо(х) № Иомно вмзввтн через имя экземпляре или класса '80Е' Обычно заполнение классов производится внутри инструкции с1авв, а атрибуты экземпляров создаются в результате присваивания значений атрибутам аргумента ве1( в методах.
Однако отметим снова, что все это не является обязательным, поскольку ООП в языке Ру(Ьоп— это в основном поиск атрибутов во взаимосвязанных объектах пространств имен. В заключение В этой главе были представлены основы программирования классов в языке Ру(Ьоп. Мы изучили синтаксис инструкции с1авв и увидели, как ее можно использовать для построения дерева классов.
Мы также узнали, что интерпретатор Ру(Ьоп автоматически заполняет первый аргумент методов, что атрибуты присоединяются к объектам в дереве классов простым присваиванием и что существуют специальные имена методов перегрузки операторов, позволяющие реализовать выполнение встроенных операций в наших объектах (например, операции выражений и вывод). Закрепление пройденного Контрольные вопросы 1. Как классы связаны с модулямиу 2. Как создаются классы и экземпляры классов2 3.
Где и как создаются атрибуты классов7 4. Где и как создаются атрибуты экземпляров классово б. Что в языке Ру(Ьоп означает слово зе1( для классово В следующей главе мы продолжим изучение программирования классов и повторно рассмотрим модель, чтобы восполнить недостающие подробности, которые были опущены здесь для простоты. Мы также приступим к исследованию нескольких больших классов, обладающих большей практической ценностью. Однако для начала ответьте на контрольные вопросы, чтобы освежить в памяти основы, которые были рассмотрены здесь. 593 Закрепление пройденного 8.
9. Ответы 1. 2. 3. 4. 5. Как производится перегрузка операторов в классах на языке Ру- Фпоп? Когда может потребоваться перегрузка операторов в ваших клас- сах7 Какой метод перегрузки оператора используется наиболее часта? Какие две концепции ООП являются наиболее важными в языке Ру1 йод? Классы всегда находятся внутри модулей — они являются атрибутами объекта модуля. Классы и модули являются пространствами имен, но классы соответствуют инструкциям (а не целым файлам) и поддерживают такие понятия ООП, как экземпляры класса, наследование и перегрузка операторов.
В некотором смысле модули напоминают классы с единственным экземпляром, без наследования, которые соответствуют целым файлам. Классы создаются с помощью инструкций с1аэз, а экземпляры создаются вызовом класса, как если бы это была функция. Атрибуты класса создаются присваиванием атрибутам объекта класса. Обычно они создаются инструкциями верхнего уровня в инструкции с1аэз — каждое имя, которому будет присвоено значение внутри инструкции с1азз, становится атрибутом объекта класса (с технической точки зрения область видимости инструкции с1азз преобразуется в пространство имен атрибутов объекта класса). Атрибуты класса могут также создаваться через присваивание атрибутам класса в любом месте, где доступна ссылка на объект класса, то есть даже за пределами инструкции с1аза.
Атрибуты экземпляра создаются присваиванием атрибутам объекта экземпляра. Обычно они создаются внутри методов класса, в инструкции с1аээ — присваиванием значений атрибутам аргумента эе1г (который всегда является подразумеваемым экземпляром).