Математический анализ. 3е издание
Скачать 4.86 Mb.
|
687 ным образом, нам могло бы потребоваться явно выбрать метод __repr__ класса Lister: class Sub(Super, Lister): # Получить Super.other по наследованию __repr__ = Lister.__repr__ # Явно выбрать Lister.__repr__ Множественное наследование – это довольно сложная тема. Даже если вы поняли предыдущий параграф, все равно этот прием лучше исполь зовать осторожно и только в случае крайней необходимости. В против ном случае могут возникать ситуации, когда значение имени атрибута будет зависеть от порядка следования классов в инструкции определе ния подкласса. (Еще один пример этого приема в действии приводится в этой же главе в разделе «Классы нового стиля», где обсуждалось яв ное разрешение конфликтов имен.) Как правило, множественное наследование дает лучшие результаты, ко гда суперклассы являются максимально автономными, – поскольку они могут использоваться в разных контекстах, они не должны делать ка кихлибо предположений об именах, связанных с другими классами в дереве. Псевдочастные атрибуты, которые были изучены нами ранее, могут помочь в локализации имен, на владение которыми опирается класс, и ограничить вероятность появления конфликтов имен в супер классах, которые вы добавляете в список наследуемых классов. Напри мер, в данном случае класс Lister служит только для того, чтобы экспор тировать метод __repr__, поэтому он мог бы дать своему второму методу имя __other, чтобы избежать конфликтов с именами в других классах. Методы, классы и вложенные области видимости Эта проблема была ликвидирована в Python 2.2 введением областей видимости вложенных функций, но я сохранил это описание исклю чительно ради истории, для тех из вас, кому приходилось работать с более старыми версиями Python и с целью продемонстрировать, что происходит в случае вложения функций, когда один из уровней вло женности является классом. Классы, как и функции, обладают своими локальными областями ви димости, поэтому области видимости обладают сходными проявления ми в теле инструкции class. Кроме того, методы, по сути, являются вложенными функциями, поэтому здесь имеют место те же самые про блемы. Похоже, что путаница особенно часто возникает, когда имеют ся классы, вложенные друг в друга. В следующем примере (файл nester.py) функция generate возвращает экземпляр вложенного класса Spam. Внутри этой функции имя класса Spam находится в локальной области видимости функции generate. Но в версиях Python, появившихся до версии 2.2, внутри метода method имя класса Spam недоступно – method имеет доступ только к своей ло кальной области видимости, к области видимости модуля, вмещающе го окружающую функцию generate, и к встроенным именам: 688 Глава 26. Дополнительные возможности классов def generate(): class Spam: count = 1 def method(self): # Имя Spam недоступно: print Spam.count # Не локальное (def), не глобальное (модуль), # не встроенное return Spam() generate().method() C:\python\examples> python nester.py Traceback (innermost last): File "nester.py", line 8, in ? generate().method() File "nester.py", line 5, in method print Spam.count # Не локальное (def), не глобальное (модуль), NameError: Spam # не встроенное Этот пример будет работать в версии Python 2.2 и выше, потому что все локальные области вмещающих функций автоматически видимы для вложенных функций (включая и вложенные методы, как в данном примере). Но он не работал в версиях Python, вышедших до версии 2.2 (некоторые возможные решения приводятся ниже). Обратите внимание, что даже в версии 2.2 методам недоступна локаль ная область видимости вмещающего класса – им доступны только об ласти видимости вмещающих функций. Именно по этой причине ме тоды должны использовать аргумент self с экземпляром или имя класса, чтобы вызывать другие методы или обращаться к другим атри бутам, определенным во вмещающей инструкции class. Например, программный код метода не может использовать простое имя count, он должен использовать имя self.count или Spam.count. Если вам приходится работать с версией ниже 2.2, скажу, что сущест вует несколько способов заставить предыдущий пример работать. Са мый простой заключается в том, чтобы переместить имя Spam в область видимости вмещающего модуля с помощью глобального объявления. Поскольку методу method доступны глобальные имена в модуле, попыт ка сослаться на Spam уже не будет вызывать ошибку: def generate(): global Spam # Перенести имя Spam в область видимости модуля class Spam: count = 1 def method(self): print Spam.count # Работает: глобальное имя (вмещающий модуль) return Spam() generate().method() # Выведет 1 Лучше было бы реструктурировать программный код так, чтобы вме сто использования объявления global определение класса Spam находи лось на верхнем уровне модуля. После этого вложенный метод method Типичные проблемы при работе с классами 689 и функция generate будут отыскивать класс Spam в глобальной области видимости: def generate(): return Spam() class Spam: # Определение на верхнем уровне в модуле count = 1 def method(self): print Spam.count # Работает: глобальное имя (вмещающий модуль) generate().method() В действительности такой подход рекомендуется использовать во всех версиях Python – программный код выглядит проще, если в нем отсут ствуют вложенные классы и функции. Если вам требуется нечто более сложное и замысловатое, то можно просто избавиться от ссылки на имя Spam в методе method, используя специальный атрибут __class__, который возвращает класс объекта эк земпляра: def generate(): class Spam: count = 1 def method(self): print self.__class__.count # Работает: используется атрибут return Spam() # для получения класса generate().method() «Многослойное обертывание» При грамотном использовании способность объектноориентированно го программного кода к многократному использованию поможет су щественно снизить затраты времени на его разработку. Однако иногда неправильное использование потенциала абстракции ООП может серь езно осложнить понимание программного кода. Если классы наслоены друг на друга слишком глубоко, программный код становится малопо нятным – возможно, вам придется изучить множество классов, чтобы выяснить, что делает единственная операция. Например, однажды мне пришлось работать с библиотекой, написан ной на языке C++, содержащей тысячи классов (часть которых была сгенерирована машиной) и до 15 уровней наследования. Расшифровка вызовов методов в такой сложной системе классов часто оказывалась неподъемной задачей: даже в простейшую операцию оказывались во влеченными сразу несколько классов. Логика системы оказалась та кой многослойной, что в некоторых случаях, чтобы понять принцип действия какоголибо участка программного кода, требовалось не сколько дней копаться в нескольких файлах. Здесь также вполне применимо одно из самых универсальных правил языка Python: не усложняйте решение задачи, если оно не является 690 Глава 26. Дополнительные возможности классов таковым. Обертывание программного кода несколькими слоями клас сов на грани непостижимости – всегда плохая идея. Абстракция – это основа полиморфизма и инкапсуляции, и при грамотном использова нии она может быть весьма эффективным инструментом. Однако вы упростите отладку и сопровождение, если сделаете интерфейсы своих классов интуитивно понятными. Избегайте чрезмерной абстракции и сохраняйте иерархии своих классов короткими и плоскими, если не существует веских причин сделать иначе. В заключение В этой главе было представлено несколько расширенных возможностей классов, включая наследование встроенных типов, псевдочастные атри буты, классы нового стиля, статические методы и декораторы функций. Большинство из них являются необязательными расширениями моде ли ООП в языке Python, но они могут стать более полезными, когда вы начнете создавать крупные объектноориентированные программы. Это конец части, посвященной классам, поэтому ниже вы найдете обыч ные в этом случае упражнения – обязательно проработайте их, чтобы получить некоторую практику создания настоящих классов. В следую щей главе мы начнем изучение последней базовой темы – исключе ний. Исключения – это механизм взаимодействий с ошибками и дру гими ситуациями, возникающими в программном коде. Это относи тельно несложная тема, но я оставил ее напоследок, потому что в на стоящее время исключения оформлены в виде классов. Но прежде чем заняться этой последней темой, ознакомьтесь с контрольными вопро сами к этой главе и выполните упражнения. Закрепление пройденного Контрольные вопросы 1. Назовите два способа расширения встроенных типов. 2. Для чего используются декораторы функций? 3. Как создать класс нового стиля? 4. Чем отличаются классы нового стиля и классические классы? 5. Чем отличаются обычные и статические методы? 6. Сколько секунд нужно выждать, прежде чем бросить «Пресвятую ручную гранату»? Ответы 1. Можно заключать встроенные классы в классыобертки или насле довать встроенные типы в подклассах. Последний вариант проще, так как в этом случае подклассы наследуют большую часть поведе ния оригинальных классов. Закрепление пройденного 691 2. Декораторы функций обычно используются для добавления допол нительного слоя логики к существующим функциям, который за пускается при каждом вызове функции. Они могут использоваться для регистрации вызовов этих функций, проверки типов передавае мых аргументов и т. д. Кроме того, они используются для «объявле ния» статических методов – простых функций в классе, которым не передается экземпляр класса. 3. Классы нового стиля создаются наследованием встроенного класса object (или любого другого встроенного типа). В Python 3.0 все классы по умолчанию будут классами нового стиля. 4. При использовании ромбоидальной схемы наследования в классах нового стиля поиск в дереве наследования выполняется иначе – сна чала производится поиск в ширину, а не в высоту. Кроме того, клас сы нового стиля поддерживают ряд новых дополнительных особен ностей, включая свойства и список атрибутов экземпляра __slots__. 5. Обычные методы (экземпляра) принимают аргумент self (подразу меваемый экземпляр), а статические методы – нет. Статические ме тоды – это простые функции, вложенные в объект класса. Чтобы превратить обычный метод в статический, необходимо передать его специальной встроенной функции, или декоратору, с использова нием правил декорирования. 6. Три секунды. (Или, если быть более точным: «И сказал Господь: Допреже всего Пресвятую чеку извлечь долженствует. Опосля же того, сочти до трех, не более и не менее. Три есть цифирь, до коей счесть потребно, и сочтенья твои суть три. До четырех счесть не мо ги, паче же до двух, опричь токмо коли два предшествует трём. О пяти и речи быть не может. Аще же достигнешь ты цифири три, что есть и пребудет третьею цифирью, брось Пресвятою антиохий скою гранатою твоею во врага твоего, и оный враг, будучи ничто жен пред лицем моим, падёт.» 1 ). Упражнения к шестой части В этих упражнениях вам будет предложено написать несколько клас сов и поэкспериментировать с существующим программным кодом. Единственная проблема существующего кода состоит в том, что он дол жен существовать. Чтобы поупражняться с набором классов в упраж нении 5, вам нужно либо загрузить файл с исходными текстами из Ин тернета (читайте Предисловие) или ввести его вручную (он достаточно короткий). Поскольку программы становятся все сложнее, обязатель но ознакомьтесь с решениями в конце книги. Решения вы найдете в приложении B в разделе «Часть VI. Классы и ООП». 1 Цитата из фильма «Monty Python and the Holy Grail». Перевод взят из Ви кипедии. В русском переводе фильм вышел под названием «Монти Пайтон и Священный Грааль». – Примеч. перев. 692 Глава 26. Дополнительные возможности классов 1. Наследование. Напишите класс с именем Adder, экспортирующий метод add(self, x, y), который выводит сообщение «Not Implement ed» («Не реализовано»). Затем определите два подкласса класса Adder , которые реализуют метод add: ListAdder С методом add, который возвращает результат конкатенации двух списков из аргументов. DictAdder С методом add, который возвращает новый словарь, содержащий элементы из обоих словарей, передаваемых как аргументы (по дойдет любое определение сложения). Поэкспериментируйте с экземплярами всех трех классов в инте рактивной оболочке, вызывая их методы add. Теперь расширьте суперкласс Adder, добавив сохранение объекта в экземпляре с помощью конструктора (например, присваивая спи сок или словарь атрибуту self.data), и реализуйте перегрузку опе ратора + с помощью метода __add__ так, чтобы он автоматически вы зывал ваш метод add (например, выражение X + Y должно приводить к вызову метода X.add(X.data, Y)). Где лучше разместить методы конструктора и перегрузки оператора (то есть в каком из классов)? Объекты какого типа смогут складывать ваши классы? На практике гораздо проще написать метод add, который принима ет один действительный аргумент (например, add(self, y)) и склады вает его с текущими данными экземпляра (например, self.data + y). Будет ли в такой реализации больше смысла, чем в реализации, ко торая принимает два аргумента? Можно ли сказать, что это делает ваши классы более «объектноориентированными»? 2. Перегрузка операторов. Напишите класс с именем Mylist, который «обертывает» списки языка Python: он должен перегружать основ ные операторы действий над списками, включая +, доступ к элемен там по индексу, итерации, извлечение среза и такие методы списка, как append и sort. Полный перечень методов, поддерживаемых спи сками, вы найдете в справочном руководстве по языку Python. Кроме того, напишите конструктор для своего класса, который принимает существующий список (или экземпляр класса Mylist) и копирует его в атрибут экземпляра. Поэкспериментируйте со своим классом в ин терактивной оболочке. В ходе экспериментов выясните следующее: a. Почему здесь так важно копировать начальное значение? b. Можно ли использовать пустой срез (например, start[:]) для ко пирования начального значения, если им является Mylist? c. Существует ли универсальный способ передачи управления унаследованным методам списка? d. Можно ли складывать Mylist и обычный список? А список и My list ? Закрепление пройденного 693 e. Объект какого типа должны возвращать операции сложения и из влечения среза? А операции извлечения элементов по индексу? f. Если у вас достаточно новая версия Python (2.2 или выше), вы сможете реализовать такого рода классобертку, встраивая на стоящий список в отдельный класс или наследуя класс list. Ка кой из двух способов проще и почему? 3. Подклассы. Напишите подкласс с именем MylistSub от класса Mylist из упражнения 2, который расширяет класс Mylist возможностью вывода сообщения на stdout перед выполнением каждой перегру женной операции и подсчета числа вызовов. Класс MylistSub дол жен наследовать методы Mylist. При сложении MylistSub с последо вательностями должно выводиться сообщение, увеличиваться счет чик вызовов операции сложения и вызываться метод суперкласса. Кроме того, добавьте новый метод, который будет выводить счетчи ки операций на stdout, и поэкспериментируйте с этим классом в ин терактивной оболочке. Как работают ваши счетчики – считают ли они операции для всего класса (для всех экземпляров класса) или для каждого экземпляра в отдельности? Как бы вы реализовали ка ждый из этих случаев? (Подсказка: зависит от того, в каком объек те производится присваивание значения счетчика: атрибут класса используется всеми экземплярами, а атрибуты аргумента self хра нят данные экземпляра.) 4. Методы метакласса. Напишите класс с именем Meta с методами, которые перехватывают все обращения к атрибутам (как получение значения, так и присваивание) и выводят сообщения, перечисляю щие их аргументы, на stdout. Создайте экземпляр класса Meta и по экспериментируйте с ним в интерактивной оболочке. Что произой дет, если попытаться использовать экземпляр класса в выраже нии? Попробуйте выполнить над своим классом операции сложе ния, доступа к элементам по индексу и получения среза. 5. Объекты множеств. Поэкспериментируйте с набором классов, описанных в разделе «Расширение типов встраиванием». Выпол ните команды, которые выполняют следующие операции: a. Создайте два множества целых чисел и найдите их пересечение и объединение с помощью операторов & и |. b. Создайте множество из строки и поэкспериментируйте с извле чением элементов множества по индексу. Какой метод в классе при этом вызывается? c. Попробуйте выполнить итерации через множество, созданное из строки, с помощью цикла for. Какой метод вызывается на этот раз? d. Попробуйте найти пересечение и объединение множества, соз данного из строки, и простой строки. Возможно ли это? e. Теперь расширьте класс множества наследованием, чтобы под класс мог обрабатывать произвольное число операндов, исполь 694 Глава 26. Дополнительные возможности классов зуя для этого форму аргумента *args. (Подсказка: вернитесь к рассмотрению этих алгоритмов в главе 16.) Найдите пересече ние и объединение нескольких операндов с помощью вашего подкласса множества. Как можно реализовать вычисление пере сечения трех и более множеств, если оператор & работает всего с двумя операндами? f. Как бы вы реализовали другие операции над списками в классе множества? (Подсказка: метод __add__ перехватывает операцию конкатенации, а метод __getattr__ может передавать большинст во вызовов методов списка в обернутый список.) 6. Связи в дереве классов. В разделе «Пространства имен: окончание истории» в главе 24 и в разделе «Множественное наследование» в главе 25 я упоминал, что классы имеют атрибут __bases__, кото рый возвращает кортеж объектов суперклассов (тех, что перечисле ны в круглых скобках в заголовке инструкции class). Используя атрибут __bases__, расширьте класс Lister (глава 25) так, чтобы он выводил имена прямых суперклассов экземпляров класса. При этом первая строка в этом выводе должна выглядеть, как показано ниже (значение адреса у вас может отличаться): тов? (Подсказка: у классов имеется атрибут __dict__.) Попробуйте расширить класс Lister так, чтобы он выводил список всех доступ ных суперклассов и их атрибуты (подсказка: порядок подъема по дереву наследования описывается в примере classtree.py в главе 24 и в сноске об использовании функций dir и getattr в Python 2.2 в разделе «Множественное наследование» в главе 25). 7. Композиция. Сымитируйте сценарий оформления заказа в рестора не быстрого питания, определив четыре класса: Lunch Вмещающий и управляющий класс. Customer Действующее лицо, покупающее блюдо. Employee Действующее лицо, принимающее заказ. Food То, что приобретает заказчик. Чтобы вам было с чего начать, определите следующие классы и ме тоды: class Lunch: def __init__(self) # Создает и встраивает Customer и Employee def order(self, foodName) # Имитирует прием заказа def result(self) # Запрашивает у клиента название блюда |