Математический анализ. 3е издание
Скачать 4.86 Mb.
|
598 Глава 24. Подробнее о программировании классов объекта всегда изменяет сам объект, а не чтото другое. 1 Например, ат рибут y.spam будет найден в наследуемом классе, а операция присваи вания атрибуту x.spam присоединит имя непосредственно к объекту x. Ниже приводится более понятный пример этого поведения, где одно и то же имя создается в двух местах. Предположим, что мы использу ем следующий класс: class MixedNames: # Определение класса data = 'spam' # Присваивание атрибуту класса def __init__(self, value): # Присваивание имени метода self.data = value # Присваивание атрибуту экземпляра def display(self): print self.data, MixedNames.data # Атрибут экземпляра, # атрибут класса Этот класс содержит две инструкции def, которые связывают атрибу ты класса с методами. Здесь также присутствует инструкция присваи вания =. Так как эта инструкция выполняет присваивание имени data внутри инструкции class, оно создается в локальной области видимо сти класса и становится атрибутом объекта класса. Как и все атрибуты класса, атрибут data наследуется и используется всеми экземплярами класса, которые не имеют собственного атрибута data. Когда создаются экземпляры этого класса, имя data присоединяется к этим экземплярам через присваивание атрибуту self.data в конст рукторе: >>> x = MixedNames(1) # Создаются два объекта экземпляров >>> y = MixedNames(2) # каждый из которых имеет свой атрибут data >>> x.display(); y.display() # self.data это другие атрибуты, 1 spam # а Subclass.data тот же самый 2 spam Суть этого примера состоит в том, что атрибут data находится в двух разных местах: в объектах экземпляров (создаются присваиванием ат рибуту self.data в методе __init__) и в классе, от которого они наследу ют имена (создается присваиванием имени data в инструкции class). Метод класса display выводит обе версии – сначала атрибут экземпля ра self, а затем атрибут класса. Используя этот прием сохранения атрибутов в различных объектах, мы определяем их области видимости. Атрибуты классов совместно используются всеми экземплярами, а атрибуты экземпляров уникаль ны для каждого экземпляра – ни данные, ни поведение экземпляра не доступны для совместного использования. Несмотря на то что опера ция поиска в дереве наследования позволяет отыскивать имена, мы 1 При условии, что класс не переопределил операцию присваивания с помо щью метода перегрузки оператора __setattr__ с целью выполнять какието особые действия. Методы 599 всегда можем получить доступ к ним в любой точке дерева, обратив шись непосредственно к нужному объекту. В предыдущем примере, например, выражения x.data и self.data воз вращают атрибут экземпляра, которые переопределяют то же самое имя в классе. Однако выражение MixedNames.data явно обращается к ат рибуту класса. Позднее мы еще встретим подобные шаблоны програм мирования, например следующий раздел описывает один из наиболее часто используемых. Методы Вы уже знакомы с функциями и знаете о методах в классах. Методы – это обычные объекты функций, которые создаются инструкциями def в теле инструкции class. Говоря вкратце, методы реализуют поведе ние, наследуемое объектами экземпляров. С точки зрения программи рования методы работают точно так же, как и обычные функции, с од ним важным исключением: в первом аргументе методам всегда пере дается подразумеваемый объект экземпляра. Другими словами, интерпретатор автоматически отображает вызов метода экземпляра на метод класса следующим образом. Вызов метода экземпляра: instance.method(args...) автоматически преобразуется в вызов метода класса: class.method(instance, args...) где класс определяется в результате поиска имени метода по дереву наследования. Фактически в языке Python обе формы вызова метода являются допустимыми. Помимо обычного наследования имен методов, первый специальный аргумент – это единственная необычная особенность методов. Первый аргумент в методах классов обычно называется self, в соответствии с общепринятыми соглашениями (с технической точки зрения само имя не играет никакой роли, значение имеет позиция аргумента). Этот ар гумент обеспечивает доступ к экземпляру, то есть к субъекту вызова – поскольку из классов может создаваться множество объектов экземп ляров, этот аргумент необходим для доступа к данным конкретного экземпляра. Программисты, знакомые с языком C++, сочтут, что аргумент self в языке Python напоминает указатель this в языке C++. Однако в язы ке Python имя self всегда явно используется в программном коде: ме тоды всегда должны использовать имя self для получения или измене ния атрибутов экземпляра, обрабатываемого текущим вызовом мето да. Такая явная природа аргумента self предусмотрена намеренно – присутствие этого имени делает очевидным использование имен атри бутов экземпляра. 600 Глава 24. Подробнее о программировании классов Пример Чтобы пояснить эти концепции, обратимся к примеру. Предположим, что имеется следующее определение класса: class NextClass: # Определение класса def printer(self, text): # Определение метода self.message = text # Изменение экземпляра print self.message # Обращение к экземпляру Имя printer ссылается на объект функции, а так как оно создается в об ласти видимости инструкции class, оно становится атрибутом объекта класса и будет унаследовано всеми экземплярами, которые будут созда ны из класса. Обычно методы, такие как printer, предназначены для обработки экземпляров, поэтому мы вызываем их через экземпляры: >>> x = NextClass() # Создать экземпляр >>> x.printer('instance call') # Вызвать его метод instance call >>> x.message # Экземпляр изменился 'instance call' Когда метод вызывается с использованием составного имени экземп ляра, как в данном случае, то сначала определяется местонахождение метода printer, а затем его аргументу self автоматически присваивает ся объект экземпляра (x). В аргумент text записывается строка, пере данная в вызов метода ('instance call'). Обратите внимание, что Py thon автоматически передает в первом аргументе self ссылку на сам экземпляр, поэтому нам достаточно передать методу только один аргу мент. Внутри метода printer имя self используется для доступа к дан ным конкретного экземпляра, потому что оно ссылается на текущий обрабатываемый экземпляр. Методы могут вызываться любым из двух способов – через экземпляр или через сам класс. Например, метод printer может быть вызван с ис пользованием имени класса, явно передавая при этом экземпляр в ар гументе self: >>> NextClass.printer(x, 'class call') # Прямой вызов метода класса class call >>> x.message # Экземпляр снова изменился 'class call' Вызов метода, который производится через экземпляр и через имя класса, оказывает одинаковое воздействие при условии, что при вызо ве через имя класса передается тот же самый экземпляр. По умолча нию, если попытаться вызвать метод без указания экземпляра, будет выведено сообщение об ошибке: >>> NextClass.printer('bad call') TypeError: unbound method printer() must be called with NextClass instance... Методы 601 (TypeError: несвязанный метод printer() должен вызываться с экземпляром NextClass...) Вызов конструкторов суперклассов Обычно методы вызываются через экземпляры. Тем не менее вызовы методов через имя класса могут играть особую роль. Одна из таких ро лей связана с вызовом конструктора. Метод __init__ наследуется точно так же, как и любые другие атрибуты. Это означает, что во время соз дания экземпляра интерпретатор отыскивает только один метод __init__ . Если в конструкторе подкласса необходимо гарантировать выполнение действий, предусматриваемых конструктором суперклас са, необходимо явно вызвать метод __init__ через имя класса: class Super: def __init__(self, x): ...программный код по умолчанию... class Sub(Super): def __init__(self, x, y): Super.__init__(self, x) # Вызов метода __init__ суперкласса ...адаптированный код... # Выполнить дополнительные действия I = Sub(1, 2) Это один из немногих случаев, когда вашему программному коду по требуется явно вызывать метод перегрузки оператора. Естественно, вызывать конструктор суперкласса таким способом следует, только если это действительно необходимо – без этого вызова подкласс полно стью переопределяет данный метод. Более реалистичный случай при менения этого приема приводится в заключительном примере этой главы. 1 Другие возможности методов Такой способ вызова методов через имя класса представляет собой ос нову для расширения (без полной замены) поведения унаследованных методов. В главе 26 мы познакомимся с еще одной возможностью, до бавленной в Python 2.2, статическими методами, которые не пред полагают наличие объекта экземпляра в первом аргументе. Такие ме тоды могут действовать как обычные функции, имена которых явля ются локальными по отношению к классам, где они были определены. Однако это дополнительное расширение не является обязательным – обычно нам всегда бывает необходимо передавать экземпляр методам, вызываемым либо через сам экземпляр, либо через имя класса. 1 Небольшое замечание: внутри одного и того же класса можно определить несколько методов с именем __init__, но использоваться будет только по следнее определение. Дополнительные подробности приводятся в главе 25. 602 Глава 24. Подробнее о программировании классов Наследование Основное назначение такого инструмента пространств имен, как инст рукция class, заключается в обеспечении поддержки наследования имен. В этом разделе мы подробно остановимся на вопросах, связан ных с механизмами наследования атрибутов в языке Python. В языке Python наследование включается в игру после того, как объект квалифицирован, и заключается в операции поиска в дереве определе ний атрибутов (в одном или более пространствах имен). Каждый раз, когда используется выражение вида object.attr (где object – это объект экземпляра или класса), интерпретатор приступает к поиску первого вхождения атрибута attr в дереве пространств имен снизу вверх, начи ная с объекта object. Сюда относятся и ссылки на атрибуты аргумента self внутри методов. Поскольку самые нижние определения в дереве наследования переопределяют те, что находятся выше, механизм на следования составляет основу специализации программного кода. Создание дерева атрибутов На рис. 24.1 приводятся способы, которыми создаются и заполняются именами деревья пространств имен. Вообще: • Атрибуты экземпляров создаются посредством присваивания атри бутам аргумента self в методах. Экземпляр Объекты Программа Суперкласс Суперкласс Класс class X(S1, S2): def attr(self,...): self.attr = V class S1: class S2: object = X() object.attr? Рис. 24.1. Программный код создает дерево наследования объектов в памяти, в котором будет выполняться поиск атрибутов. Вызов класса создает новый экземпляр, который помнит, к какому классу он принадлежит. Инструкция class, в заголовке которой в круглых скобках перечислены суперклассы, создает новый класс. Каждое обращение к атрибуту вызывает новую процедуру поиска в дереве наследования снизу вверх – даже обращения к атрибутам аргумента self в методах класса Наследование 603 • Атрибуты классов создаются инструкциями (присваивания), рас положенными внутри инструкции class. • Ссылки на суперклассы создаются путем перечисления классов в круглых скобках в заголовке инструкции class. Результатом является дерево пространств имен с атрибутами, которое ведет в направлении от экземпляров к классам, из которых они были созданы, и ко всем суперклассам, перечисленным в заголовке инст рукции class. Интерпретатор выполняет поиск в дереве в направлении снизу вверх, от экземпляров к суперклассам, всякий раз, когда ис пользуемое имя подразумевает атрибут объекта экземпляра. 1 Специализация унаследованных методов Только что описанная модель поиска в дереве наследования представ ляет собой прекрасный способ специализации программ. Поскольку механизм наследования сначала пытается отыскать имена в подклас сах и только потом в их суперклассах, подклассы могут изменять пове дение по умолчанию, предусматриваемое атрибутами их суперклассов. Фактически можно создавать целые системы как иерархии классов, возможности которых расширяются за счет добавления новых под классов, а не за счет изменения существующего программного кода. Идея переопределения унаследованных имен приводит к множеству приемов специализации. Например, подклассы могут полностью заме+ щать унаследованные атрибуты, предоставлять атрибуты, которые ожидается отыскать в суперклассах, и расширять методы суперклас са за счет их вызова из методов подкласса. Мы уже видели прием с за мещением в действии. Ниже приводится пример, демонстрирующий, как выполняется расширение: >>> class Super: ... def method(self): ... print 'in Super.method' >>> class Sub(Super): ... def method(self): # Переопределить метод ... print 'starting Sub.method' # Дополнительное действие ... Super.method(self) # Выполнить действие по умолчанию ... print 'ending Sub.method' Главное здесь – это прямые вызовы методов суперкласса. Класс Sub за мещает метод method класса Super своей собственной, специализирован 1 Это описание далеко не полное, потому что точно так же возможно созда вать атрибуты экземпляров и классов с помощью инструкций присваива ния за пределами инструкций class – но этот прием используется сущест венно реже и зачастую более подвержен ошибкам (изменения не изолирова ны от инструкций class) . В языке Python все атрибуты всегда доступны по умолчанию – более подробно о сокрытии данных мы поговорим в главе 26. 604 Глава 24. Подробнее о программировании классов ной версией. Но внутри замещающего метода в классе Sub производит ся вызов версии, экспортируемой классом Super, чтобы выполнить дей ствия по умолчанию. Другими словами, метод Sub.method не замещает полностью метод Super.method, а просто расширяет его: >>> x = Super() # Создать экземпляр класса Super >>> x.method() # Вызвать Super.method in Super.method >>> x = Sub() # Создать экземпляр класса Sub >>> x.method() # Вызвать Sub.method, который вызовет Super.method starting Sub.method in Super.method ending Sub.method Этот прием расширения также часто используется в конструкторах, за примерами обращайтесь к предыдущему разделу «Методы». Приемы организации взаимодействия классов Расширение – это лишь один из способов организации взаимодействий с суперклассом. В файле ниже, specialize.py, определяется несколько классов, которые иллюстрируют различные приемы использования классов: Super Определяет метод method и метод delegate, который предполагает на личие метода action в подклассе. Inheritor Не предоставляет никаких новых имен, поэтому он получает все, что определено только в классе Super. Replacer Переопределяет метод method класса Super своей собственной версией. Extender Адаптирует метод method класса Super, переопределяя и вызывая его, чтобы выполнить действия, предусмотренные по умолчанию. Provider Реализует метод action, который ожидается методом delegate клас са Super. Рассмотрим каждый из этих классов, чтобы получить представление о способах, которыми они адаптируют свой общий суперкласс. Содер жимое самого файла приводится ниже: class Super: def method(self): print 'in Super.method' # Поведение по умолчанию def delegate(self): self.action() # Ожидаемый метод Наследование 605 class Inheritor(Super): # Наследует методы, как они есть pass class Replacer(Super): # Полностью замещает method def method(self): print 'in Replacer.method' class Extender(Super): # Расширяет поведение метода method def method(self): print 'starting Extender.method' Super.method(self) print 'ending Extender.method' class Provider(Super): # Определяет необходимый метод def action(self): print 'in Provider.action' if __name__ == '__main__': for klass in (Inheritor, Replacer, Extender): print '\n' + klass.__name__ + '...' klass().method() print '\nProvider...' x = Provider() x.delegate() Здесь следует отметить несколько моментов. Программный код тести рования модуля в конце примера создает экземпляры трех разных классов в цикле for. Поскольку классы – это объекты, можно помес тить их в кортеж и создавать экземпляры единообразным способом (подробнее об этой идее рассказывается ниже). Кроме всего прочего, классы, как и модули, имеют атрибут __name__ – он содержит строку с именем класса, указанным в заголовке инструкции class. Ниже по казано, что произойдет, если запустить файл: % python specialize.py Inheritor... in Super.method Replacer... in Replacer.method Extender... starting Extender.method in Super.method ending Extender.method Provider... in Provider.action Абстрактные суперклассы Обратите внимание, как работает класс Provider в предыдущем приме ре. Когда через экземпляр класса Provider вызывается метод delegate, инициируются две независимые процедуры поиска: |