Главная страница
Навигация по странице:

  • Ссылки на пространства имен

  • Более реалистичный пример

  • Закрепление пройденного Контрольные вопросы

  • Ответы 1. Абстрактный суперкласс – это класс, который вызывает методы, но не наследует и не определяет их. Он ожидает, что методы будут реа 636

  • Использование перегрузки путем сигнатур вызова (или отказ от нее)

  • Математический анализ. 3е издание


    Скачать 4.86 Mb.
    Название3е издание
    АнкорМатематический анализ
    Дата04.02.2022
    Размер4.86 Mb.
    Формат файлаpdf
    Имя файлаpython_01.pdf
    ТипДокументы
    #351981
    страница77 из 98
    1   ...   73   74   75   76   77   78   79   80   ...   98
    X.__dict__
    {'data1': 'spam', 'data3': 'ham', 'data2': 'eggs'}
    1
    Содержимое словарей атрибутов и результаты вызова функции dir могут отличаться. Например, т.к. теперь интерпретатор позволяет встроенным типам классифицировать себя как классы, для встроенных типов функция dir включает информацию о методах перегрузки операторов. Вообще, име
    на атрибутов, начинающиеся и завершающиеся двумя символами подчер
    кивания, являются особенностью интерпретатора. Подклассы типов будут рассматриваться в главе 26.

    630
    Глава 24. Подробнее о программировании классов
    >>> X.__dict__.keys()
    ['data1', 'data3', 'data2']
    >>>> dir(X)
    ['__doc__', '__module__', 'data1', 'data2', 'data3', 'hello', 'hola']
    >>> dir(sub)
    ['__doc__', '__module__', 'hello', 'hola']
    >>> dir(super)
    ['__doc__', '__module__', 'hello']
    Поэкспериментируйте самостоятельно с этими специальными атрибу
    тами, чтобы получить представление о том, как в действительности ве
    дется работа с атрибутами. Даже если вы никогда не будете использо
    вать их в своих программах, понимание того, что пространства имен –
    это всего лишь обычные словари, поможет лишить покрова таинствен
    ности само понятие пространств имен.
    Ссылки на пространства имен
    В предыдущем разделе были представленны атрибуты экземпляра и класса __class__ и __bases__, но не объяснялось, зачем они могут по
    надобиться. В двух словах, эти атрибуты позволяют осматривать ие
    рархии наследования в вашем программном коде. Например, их мож
    но использовать для отображения дерева классов на экране, как в сле
    дующем примере:
    # classtree.py def classtree(cls, indent):
    print '.'*indent, cls.__name__ # Вывести имя класса
    for supercls in cls.__bases__: # Рекурсивный обход всех суперклассов
    classtree(supercls, indent+3) # Каждый суперкласс может быть
    # посещен более одного раза
    def instancetree(inst):
    print 'Tree of', inst # Показать экземпляр
    classtree(inst.__class__, 3) # Взойти к его классу
    def selftest():
    class A: pass class B(A): pass class C(A): pass class D(B,C): pass class E: pass class F(D,E): pass instancetree(B())
    instancetree(F())
    if __name__ == '__main__': selftest()
    Функция classtree в этом сценарии является рекурсивной – она выво
    дит имя класса, используя атрибут __name__, и затем начинает подъем к суперклассам, вызывая саму себя. Это позволяет функции выпол
    нять обход деревьев классов произвольной формы – в процессе рекур

    Более реалистичный пример
    631
    сии выполняется подъем по дереву и заканчивается по достижении корневых суперклассов, у которых атрибут __bases__ пуст. Большую часть этого файла занимает программный код самотестирования – ес
    ли запустить файл как самостоятельный сценарий, он построит пустое дерево классов, создаст в нем два экземпляра и выведет структуры классов, соответствующие им:
    % python classtree.py
    Tree of <__main__.B instance at 0x00ACB438>
    ... B
    ...... A
    Tree of <__main__.F instance at 0x00AC4DA8>
    ... F
    ...... D
    ......... B
    ............ A
    ......... C
    ............ A
    ...... E
    Отступы, отмеченные точками, обозначают высоту в дереве классов.
    Конечно, мы могли бы улучшить формат вывода и даже отобразить де
    рево в графическом интерфейсе.
    Мы можем импортировать эти функции везде, где нам может потребо
    ваться быстро отобразить дерево классов:
    >>> class Emp: pass
    >>> class Person(Emp): pass
    >>> bob = Person()
    >>> import classtree
    >>> classtree.instancetree(bob)
    Tree of <__main__.Person instance at 0x00AD34E8>
    ... Person
    ...... Emp
    Независимо от того, будете вы создавать и использовать нечто подобное в своей практике или нет, этот пример демонстрирует один из многих способов использования специальных атрибутов, которые создаются внутренними механизмами интерпретатора. Еще один пример вы уви
    дите в разделе «Множественное наследование» в главе 25, где будет реализован класс общего назначения для получения списка атрибутов.
    Более реалистичный пример
    Большая часть примеров, которые мы видели до сих пор, были искус
    ственными и ограниченными и были предназначены, чтобы помочь вам сосредоточить свое внимание на основах. Однако мы закончим эту главу большим примером, в котором воедино собирается многое из то

    632
    Глава 24. Подробнее о программировании классов го, что мы уже изучили. Я включил этот пример по большей части как упражнение для самостоятельного изучения – попробуйте вниматель
    но прочитать программный код примера и понять, как разрешаются вызовы методов.
    Если говорить двух словах, следующий модуль, person.py, определяет три класса:

    GenericDisplay
    – это смешанный класс, который предоставляет уни
    версальный метод __str__; для любого класса, который будет насле
    довать его, данный метод возвращает строку, содержащую имя класса, из которого был создан данный экземпляр, а также пары
    «имя = значение» для всех атрибутов в дереве наследования. Для построения списка пар «имя = значение» он использует атрибут пространства имен __dict__. А для определения имени класса ис
    пользуется встроенный атрибут __name__ атрибута экземпляра
    __class__
    . Так как инструкция print вызывает метод __str__, при вы
    воде всех экземпляров, наследующих поведение этого класса, будет использован собственный формат вывода. Это общий инструмент.

    Person хранит общую информацию о человеке и предоставляет два метода, которые используются для изменения информации о со
    стоянии объекта. Кроме того, от своего суперкласса он наследует логику вывода в нестандартном формате. Экземпляр класса Person имеет два атрибута и два метода, управляющие этим классом.

    Employee
    – это адаптация класса Person, которая наследует извлече
    ние отчества и вывод в нестандартном формате, а также добавляет новый метод, с помощью которого выполняется увеличение зарпла
    ты служащего, и переопределяет операцию изменения возраста
    (очевидно, служащие стареют быстрее, чем другие люди). Обратите внимание, что конструктор суперкласса вызывается вручную – нам необходимо выполнить версию суперкласса, чтобы заполнить атри
    буты name и age.
    По мере изучения программного кода этого модуля вы увидите, что ка
    ждый экземпляр имеет свою собственную информацию о состоянии.
    Обратите внимание, как используется наследование для адаптации поведения и как используется перегрузка операторов для инициализа
    ции и вывода экземпляров:
    # person.py class GenericDisplay:
    def gatherAttrs(self):
    attrs = '\n'
    for key in self.__dict__:
    attrs += '\t%s=%s\n' % (key, self.__dict__[key])
    return attrs def __str__(self):
    return '<%s: %s>' % (self.__class__.__name__, self.gatherAttrs())
    class Person(GenericDisplay):

    Более реалистичный пример
    633
    def __init__(self, name, age):
    self.name = name self.age = age def lastName(self):
    return self.name.split()[1]
    def birthDay(self):
    self.age += 1
    class Employee(Person):
    def __init__(self, name, age, job=None, pay=0):
    Person.__init__(self, name, age)
    self.job = job self.pay = pay def birthDay(self):
    self.age += 2
    def giveRaise(self, percent):
    self.pay *= (1.0 + percent)
    if __name__ == '__main__':
    bob = Person('Bob Smith', 40)
    print bob print bob.lastName()
    bob.birthDay()
    print bob sue = Employee('Sue Jones', 44, job='dev', pay=100000)
    print sue print sue.lastName()
    sue.birthDay()
    sue.giveRaise(.10)
    print sue
    Чтобы проверить этот программный код, его можно импортировать и создать экземпляры в интерактивной оболочке. Ниже приводится пример класса Person в действии. Создание экземпляра вызывает ме
    тод __init__, вызов именованных методов использует или изменяет ин
    формацию о состоянии экземпляра (атрибутов), а попытка вывести эк
    земпляр вызывает унаследованный метод __str__, что обеспечивает единообразный формат вывода всех атрибутов:
    >>> from person import Person
    >>> ann = Person('Ann Smith', 45)
    >>> ann.lastName()
    'Smith'
    >>> ann.birthDay()
    >>> ann.age
    46
    >>> print ann
    age=46
    name=Ann Smith
    >

    634
    Глава 24. Подробнее о программировании классов
    Наконец, ниже приводятся результаты выполнения программного ко
    да самопроверки (в нижней части файла, вслед за проверкой атрибута
    __name__
    ), который создает экземпляры классов Person и Employee и из
    меняет каждый из них. Как обычно, программный код самопроверки выполняется, только если файл запускается как самостоятельный сценарий, а не импортируется как библиотечный модуль. Обратите внимание, как класс Employee наследует реализацию операции форма
    тированного вывода и извлечения отчества, добавляет дополнитель
    ную информацию о состоянии, добавляет метод для получения надбав
    ки и адаптирует версию метода увеличения возраста (сотрудники ста
    реют в два раза быстрее!):
    % python person.py
    age=40
    name=Bob Smith
    >
    Smith age=41
    name=Bob Smith
    >
    job=dev pay=100000
    age=44
    name=Sue Jones
    >
    Jones
    job=dev pay=110000.0
    age=46
    name=Sue Jones
    >
    Проследите, как работает программный код в этом примере, чтобы по
    нять, как эти результаты отражают вызовы методов, – в этом примере сведена воедино большая часть идей, составляющих основу ООП.
    Теперь, когда вы познакомились с классами в языке Python, вы навер
    няка оцените тот факт, что использованные здесь классы – это немно
    гим более, чем пакеты функций, которые внедряют и манипулируют встроенными объектами, присоединенными к атрибутам экземпляра в качестве информации о состоянии. Например, когда метод lastName разбивает строку и извлекает последнюю часть, он просто применяет встроенные операции обработки строки и списка к объекту, управляе
    мому классом.
    Перегрузка операторов и наследование – автоматический поиск атри
    бутов в подразумеваемом дереве классов – это главные механизмы

    В заключение
    635
    ООП. В конечном итоге именно они позволяют классу Employee, нахо
    дящемуся в самом низу дерева, получать большую часть своего поведе
    ния «просто так», что составляет главную идею ООП.
    В заключение
    В этой главе был предпринят второй, более глубокий тур по механиз
    мам ООП в языке Python. Мы узнали еще больше о классах и методах,
    о наследовании и методах перегрузки операторов. Мы также закончи
    ли повествование о пространствах имен в языке Python, расширив это понятие, чтобы охватить его применение к классам. По пути мы рас
    смотрели еще несколько дополнительных концепций, таких как абст
    рактные суперклассы, атрибуты данных класса и вызов методов и кон
    структоров суперкласса вручную. В заключение мы изучили большой пример, соединяющий в себе многое из того, что мы уже знаем об ООП.
    Теперь, когда мы знаем все о программировании классов в языке Py
    thon, в следующей главе мы обратимся к некоторым распространенным шаблонам проектирования – способам использования и комбинирова
    ния классов для оптимизации многократного использования программ
    ного кода. Часть сведений в этой главе напрямую не связана с языком
    Python, но ее необходимо знать для грамотной работы с классами. Одна
    ко, прежде чем приступить к чтению, ответьте на обычные контрольные вопросы, чтобы освежить в памяти все, о чем говорилось в этой главе.
    Закрепление пройденного
    Контрольные вопросы
    1. Что такое абстрактный суперкласс?
    2. Какие два метода перегрузки операторов можно использовать для поддержки итераций в классах?
    3. Что произойдет, когда простая инструкция присваивания появится на верхнем уровне в инструкции class?
    4. Зачем может потребоваться в классе вручную вызывать метод
    __init__
    суперкласса?
    5. Как можно расширить унаследованный метод вместо полного его замещения?
    6. Какие методы вызываются в последнем примере этой главы, когда выводится экземпляр sue класса Employee?
    7. Назовите… столицу Ассирии.
    Ответы
    1. Абстрактный суперкласс – это класс, который вызывает методы, но не наследует и не определяет их. Он ожидает, что методы будут реа

    636
    Глава 24. Подробнее о программировании классов лизованы в подклассах. Часто такой прием используется для обоб
    щения классов, когда поведение будущих подклассов трудно пред
    сказать заранее. Платформы ООП также используют этот прием для выполнения операций, определяемых клиентом.
    2. Классы могут обеспечить поддержку итераций, определив (или унас
    ледовав) метод __getitem__ или __iter__. Во всех итерационных кон
    текстах интерпретатор Python сначала пытается использовать метод
    __iter__
    (который возвращает объект, поддерживающий итерацион
    ный протокол в виде метода next): если метод __iter__ не будет най
    ден в результате поиска по дереву наследования, интерпретатор воз
    вращается к использованию метода извлечения элемента по его ин
    дексу __getitem__ (который вызывается многократно и при каждом вызове получает постоянно увеличивающиеся значения индексов).
    3. Когда простой оператор присваивания (X = Y) появляется на верх
    нем уровне в инструкции class, он присоединяет к классу атрибут данных (Class,X). Как и все атрибуты класса, этот атрибут будет со
    вместно использоваться всеми экземплярами. При этом атрибуты данных не являются вызываемыми методами.
    4. Вручную вызывать метод __init__ суперкласса может потребовать
    ся, когда класс определяет свой собственный конструктор __init__
    и при этом необходимо, чтобы выполнялись действия, предусмот
    ренные конструктором суперкласса. Интерпретатор Python автома
    тически вызывает только один конструктор – самый нижний в де
    реве наследования. Конструктор суперкласса вызывается через имя класса, и ему вручную передается аргумент self: Super
    class.__init__(self, ...)
    5. Чтобы расширить унаследованный метод вместо полного его заме
    щения, нужно переопределить его в подклассе и при этом вручную вызвать версию метода суперкласса из нового метода в подклассе.
    То есть вручную передать версии метода суперкласса аргумент self:
    Superclass.method(self, ...)
    6. В конечном итоге при выводе экземпляра sue будет вызван метод Ge
    nericDisplay.__str__
    , который в свою очередь вызовет метод Gene
    ricDisplay.gatherAttrs
    . Подробнее, при печати экземпляра sue инст
    рукция print преобразует информацию в объекте в удобочитаемую строку, передавая объект встроенной функции str. Для класса это означает попытку отыскать в дереве наследования метод перегрузки оператора __str__ и, в случае успеха, его вызов. Объект sue является экземпляром класса Employee, который сам не имеет метода __str__,
    поэтому следующий цикл поиска производится в классе Person и ме
    тод __str__ будет найден в конце концов в классе GenericDisplay.
    7. Ашшур (или КалатШеркат), Калах (или Нимруд), короткое время был столицей ДурШаррукин (или Хорсабад) и, наконец, Ниневия.

    25
    Шаблоны проектирования с классами
    До сих пор в этой части книги мы все свое внимание уделяли использо
    ванию объектноориентированных инструментов языка Python –
    классов. Но ООП – это еще и задача проектирования: как использовать классы для моделирования полезных объектов. В этой главе мы кос
    немся некоторых базовых идей ООП и рассмотрим несколько дополни
    тельных примеров, более реалистичных, чем мы видели до сих пор.
    Многие термины, упоминающиеся здесь (делегирование, композиция,
    фабрики и другие), требуют более подробного пояснения, чем я приво
    дил ранее в этой книге. Если эта тема вызывает у вас интерес, я пред
    лагаю в качестве следующего шага взяться за изучение книг, посвя
    щенных шаблонам проектирования в ООП.
    Python и ООП
    Реализацию ООП в языке Python можно свести к трем следующим идеям:
    Наследование
    Наследование основано на механизме поиска атрибутов в языке Py
    thon (в выражении X.name).
    Полиморфизм
    Назначение метода method в выражении X.method зависит от типа
    (класса) X.
    Инкапсуляция
    Методы и операторы реализуют поведение; сокрытие данных – это соглашение по умолчанию.
    К настоящему времени вы уже должны иметь представление о том, что такое наследование в языке Python. Кроме того, мы уже несколько раз говорили о полиморфизме в языке Python – он произрастает из отсутст

    638
    Глава 25. Шаблоны проектирования с классами вия объявления типов в языке Python. Поскольку разрешение имен ат
    рибутов производится на этапе выполнения, объекты, реализующие одинаковые интерфейсы, являются взаимозаменяемыми – клиентам не требуется знать тип объекта, реализующего вызываемый метод.
    Инкапсуляция в языке Python означает упаковывание – то есть со
    крытие подробностей реализации за интерфейсом объекта. Это не оз
    начает принудительное сокрытие, как будет показано в главе 26. Ин
    капсуляция позволяет изменять реализацию интерфейсов объекта, не оказывая влияния на пользователей этого объекта.
    Использование перегрузки путем сигнатур вызова
    (или отказ от нее)
    В некоторых объектноориентированных языках под полиморфизмом также понимается возможность перегрузки функций, основанной на сигнатурах типов их аргументов. Но, так как в языке Python отсутст
    вуют объявления типов, эта концепция в действительности здесь не применима – полиморфизм в языке Python основан на интерфейсах
    объектов, а не на типах.
    Вы можете попробовать выполнить перегрузку методов, изменяя спи
    ски их аргументов, как показано ниже:
    class C:
    def meth(self, x):
    def meth(self, x, y, z):
    Это вполне работоспособный программный код, но, так как инструк
    ция def просто присваивает объект имени в области видимости класса,
    сохранено будет только последнее определение метода (это все равно,
    что записать две инструкции подряд: X = 1, а затем X = 2, в результате чего X будет иметь значение 2).
    Выбор на основе типа всегда можно реализовать с помощью идеи про
    верки типа, с которой мы встречались в главах 4 и 9, или с помощью возможности передачи списка аргументов, обсуждавшейся в главе 16:
    class C:
    def meth(self, *args):
    if len(args) == 1:
    elif type(arg[0]) == int:
    Однако обычно этого следует избегать, потому что, как описывалось в главе 15, следует писать такой код, который полагается на интер
    фейс объекта, а не на конкретный тип данных. Такой подход полезнее,
    так как охватывает более широкие категории типов и приложений,
    как нынешних, так и тех, что появятся в будущем:

    Классы как записи
    1   ...   73   74   75   76   77   78   79   80   ...   98


    написать администратору сайта