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

  • Псевдочастные атрибуты класса

  • Об искажении имен в общих чертах

  • Для чего нужны псевдочастные атрибуты

  • Ромбоидальное наследование

  • Пример ромбоидального наследования В качестве иллюстрации рассмотрим следующую упрощенную реализацию ромбоидального наследования для классических классов:>>> class A: attr = 1

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


    Скачать 4.86 Mb.
    Название3е издание
    АнкорМатематический анализ
    Дата04.02.2022
    Размер4.86 Mb.
    Формат файлаpdf
    Имя файлаpython_01.pdf
    ТипДокументы
    #351981
    страница81 из 98
    1   ...   77   78   79   80   81   82   83   84   ...   98
    python typesubclass.py
    ['a', 'b', 'c']
    ['a', 'b', 'c']
    (indexing ['a', 'b', 'c'] at 1)
    a
    (indexing ['a', 'b', 'c'] at 3)

    664
    Глава 26. Дополнительные возможности классов c
    ['a', 'b', 'c', 'spam']
    ['spam', 'c', 'b', 'a']
    В эти результаты включен текст, который выводит метод класса при выполнении индексирования. Является ли изменение способа индек
    сирования универсально хорошей идеей, это уже другой вопрос: поль
    зователи вашего класса MyList могут быть повергнуты в недоумение та
    ким отступлением от общепринятого поведения последовательностей в языке Python. Однако возможность адаптировать встроенные типы подобным образом может оказаться мощным инструментом.
    Например, такой шаблон дает начало новому способу реализации мно
    жеств – в виде подкласса встроенного списка, а не в виде самостоятель
    ного класса, который управляет встроенным в него объектом списка.
    Следующий пример реализации класса в файле setsubclass.py адапти
    рует списки, добавляя методы и операторы, используемые для работы с множествами. Все остальное поведение наследуется от суперкласса list
    , поэтому альтернатива получилась более короткой и простой:
    class Set(list):
    def __init__(self, value = []): # Конструктор
    list.__init__([]) # Адаптирует список
    self.concat(value) # Копировать изменяемый аргумент по умолчанию
    def intersect(self, other): # other – любая последовательность
    res = [] # self – подразумеваемый объект
    for x in self:
    if x in other: # Выбрать общие элементы
    res.append(x)
    return Set(res) # Вернуть новый экземпляр Set
    def union(self, other): # other – любая последовательность
    res = Set(self) # Копировать меня и мой список
    res.concat(other)
    return res def concat(self, value): # аргумент value: list, Set...
    for x in value: # Удалить дубликаты
    if not x in self:
    self.append(x)
    def __and__(self, other): return self.intersect(other)
    def __or__(self, other): return self.union(other)
    def __repr__(self): return 'Set:' + list.__repr__(self)
    if __name__ == '__main__':
    x = Set([1,3,5,7])
    y = Set([2,1,4,5,6])
    print x, y, len(x)
    print x.intersect(y), y.union(x)
    print x & y, x | y x.reverse(); print x

    Псевдочастные атрибуты класса
    665
    Ниже приводится вывод, полученный в результате выполнения кода самопроверки, находящегося в конце файла. Поскольку проблема на
    следования встроенных типов достаточно сложна, я опущу дальней
    шие подробности, но я предлагаю внимательно посмотреть на полу
    ченные результаты, чтобы изучить поведение подкласса:
    % python setsubclass.py
    Set:[1, 3, 5, 7] Set:[2, 1, 4, 5, 6] 4
    Set:[1, 5] Set:[2, 1, 4, 5, 6, 3, 7]
    Set:[1, 5] Set:[1, 3, 5, 7, 2, 4, 6]
    Set:[7, 5, 3, 1]
    Есть и более эффективные способы реализации множеств – с помощью словарей, позволяющих заменить последовательное сканирование, ис
    пользуемое в данной реализации, на операцию обращения по ключу
    (хеширование) и тем самым повысить скорость работы. (За дополни
    тельной информацией обращайтесь к книге «Programming Python».)
    Если вас заинтересовали множества, тогда вам также стоит взглянуть на тип объектов set, который рассматривался в главе 5, – этот встроен
    ный тип реализует операции над множествами. Реализация операций над множествами прекрасно подходит для нужд обучения, но создавать такие реализации в современных версиях Python больше не требуется.
    В качестве другого примера наследования можно привести новый тип bool
    , появившийся в Python 2.3. Как упоминалось ранее в этой книге,
    bool
    – это подкласс типа int с двумя экземплярами (True и False), кото
    рые ведут себя как целые числа 1 и 0, но наследуют измененные вер
    сии методов вывода, особым образом отображающие их имена.
    Псевдочастные атрибуты класса
    В четвертой части книги мы узнали, что все имена, для которых вы
    полняется присваивание на верхнем уровне модуля, становятся гло
    бальными для этого модуля. То же самое по умолчанию относится и к классам – сокрытие данных регулируется соглашениями, и клиен
    ты могут получать и изменять любые атрибуты класса или экземпляра по своему усмотрению. Фактически все атрибуты являются «общедос
    тупными» (public) и «виртуальными» (virtual), если говорить в терми
    нах языка C++, – они доступны отовсюду и динамически отыскивают
    ся во время выполнения.
    1 1
    Это излишне пугает программистов, работающих с языком C++. В языке
    Python возможно даже изменить или полностью удалить метод класса во время выполнения программы. С другой стороны, почти никто не исполь
    зует такие возможности на практике. Как язык сценариев, Python больше печется о том, чтобы позволить, а не запретить. Кроме того, вспомните об
    суждение перегрузки операторов в главе 24, где говорилось, что методы
    __getattr__
    и __setattr__ могут использоваться для имитации поведения ча
    стных атрибутов, но на практике эта возможность обычно не используется.

    666
    Глава 26. Дополнительные возможности классов
    На сегодняшний день все это пока так и есть. Однако Python поддержи
    вает такое понятие, как «искажение» («mangling») имен (то есть рас
    ширение), с целью придать им черты локальных имен класса. Иска
    женные имена иногда ошибочно называют частными атрибутами, но в действительности это всего лишь способ ограничить доступ к име
    нам в классе – искажение имен не предотвращает доступ из программ
    ного кода, находящегося за пределами класса. Эта особенность в ос
    новном предназначена, чтобы избежать конфликтов имен в экземпля
    рах, а не для ограничения доступа к именам – поэтому искаженные имена лучше называть «псевдочастными», чем «частными».
    Псевдочастные имена – это совершенно необязательная возможность и вы, скорее всего, не найдете в ней особого смысла, пока не начнете писать крупные иерархии классов в проектах, над которыми трудятся несколько программистов. Но так как использование этой возможно
    сти может встретиться в программном коде других программистов,
    вам следует знать о ней, даже если вы сами не используете ее.
    Об искажении имен в общих чертах
    Здесь описывается, как действует искажение имен: имена внутри ин
    струкции class, которые начинаются с двух символов подчеркивания,
    но не заканчиваются двумя символами подчеркивания, автоматически расширяются за счет включения имени вмещающего класса. Напри
    мер, такое имя как __X в классе с именем Spam автоматически изменится на _Spam__X: к оригинальному имени будет добавлен префикс, состоя
    щий из символа подчеркивания и имени вмещающего класса, и в ре
    зультате будет получено достаточно уникальное имя, которое не будет вступать в конфликт с именами в других классах иерархии.
    Искажение имен происходит только внутри инструкций class и толь
    ко для имен, которые начинаются двумя символами подчеркивания.
    Однако это происходит со всеми именами, которые начинаются двумя символами подчеркивания, включая имена методов и имена атрибу
    тов экземпляров (например, в нашем примере с классом Spam ссылка на атрибут экземпляра self.__X будет преобразована в self._Spam__X). По
    скольку экземпляр может получать атрибуты более чем из одного класса, такое искажение позволяет избежать конфликтов, но чтобы понять, как это происходит, нам нужно рассмотреть пример.
    Для чего нужны псевдочастные атрибуты?
    Задача, которую призваны решить псевдочастные атрибуты, состоит в том, чтобы обеспечить способ сохранения атрибутов экземпляра.
    В языке Python все атрибуты экземпляра принадлежат единственно
    му объекту экземпляра, расположенному внизу дерева наследования.
    Это существенно отличается от модели языка C++, где каждый класс обладает своим собственным набором членов данных, которые он опре
    деляет.

    Псевдочастные атрибуты класса
    667
    В языке Python всякий раз, когда в пределах метода класса выполняет
    ся присваивание атрибуту аргумента self (например, self.attr = value),
    создается или изменяется атрибут экземпляра (поиск в дереве насле
    дования выполняется только при попытке получить ссылку, а не при
    своить значение). Это верно, даже когда несколько классов в иерархии выполняют присваивание одному и тому же атрибуту, поэтому кон
    фликты имен вполне возможны.
    Например, предположим, что, когда программист писал класс, он предполагал, что экземпляры этого класса будут владеть атрибутом X.
    В методах класса выполняется присваивание этому атрибуту и позднее извлекается его значение:
    class C1:
    def meth1(self): self.X = 88 # Предполагается, что X  это мой атрибут
    def meth2(self): print self.X
    Далее предположим, что другой программист, работающий отдельно,
    исходил из того же предположения, когда писал свой класс:
    class C2:
    def metha(self): self.X = 99 # И мой тоже
    def methb(self): print self.X
    Каждый класс по отдельности работает нормально. Проблема возника
    ет, когда оба класса оказываются в одном дереве наследования:
    class C3(C1, C2): ...
    I = C3() # У меня только один атрибут X!
    Теперь значение, которое получит каждый класс из выражения self.X,
    будет зависеть от того, кто из них последним присвоил значение. Все операции присваивания атрибуту self.X будут воздействовать на один и тот же экземпляр, у которого может быть только один атрибут X – I.X –
    независимо от того, сколько классов используют это имя.
    Чтобы гарантировать принадлежность атрибута тому классу, который его использует, достаточно в начале имени атрибута поставить два сим
    вола подчеркивания везде, где оно используется классом, как в сле
    дующем файле private.py:
    class C1:
    def meth1(self): self.__X = 88 # Теперь X  мой атрибут
    def meth2(self): print self.__X # Превратится в _C1__X
    class C2:
    def metha(self): self.__X = 99 # И мой тоже
    def methb(self): print self.__X # Превратится в _C2__X
    class C3(C1, C2): pass
    I = C3() # У меня два имени X
    I.meth1(); I.metha()
    print I.__dict__
    I.meth2(); I.methb()

    668
    Глава 26. Дополнительные возможности классов
    При наличии такой приставки имена атрибутов X будут дополнены именами их классов, прежде чем будут добавлены в экземпляр. Если вызвать функцию dir, чтобы просмотреть перечень атрибутов экземп
    ляра I, или просмотреть содержимое его словаря пространства имен после того, как атрибутам будут присвоены значения, можно увидеть измененные имена _C1__X и _C2__X, но не X. Такое дополнение придаст именам уникальность внутри экземпляра, поэтому разработчики клас
    сов могут рассчитывать на то, что все имена, начинающиеся с двух символов подчеркивания, действительно принадлежат их классам:
    % python private.py
    {'_C2__X': 99, '_C1__X': 88}
    88 99
    Этот прием помогает избежать конфликтов имен в экземплярах, но за
    метьте, что он не обеспечивает настоящего сокрытия данных. Если вы знаете имя атрибута внутри класса, вы сможете обратиться к нему из любой точки программы, где имеется ссылка на экземпляр, используя для этого расширенное имя (например, I._C1__X = 77). С другой сторо
    ны, эта особенность делает менее вероятным, что вы случайно вступи
    те в конфликт с существующими именами в классе.
    Еще раз отмечу, что эта особенность более полезна для крупных проек
    тов, в которых участвует несколько программистов, и только для от
    дельных имен. Не торопитесь загромождать свой программный код лишними символами без нужды – используйте эту особенность, толь
    ко когда действительно необходимо обеспечить принадлежность атри
    бута единственному классу. Для простых программ этот прием будет излишеством.
    Кроме того, посмотрите, как имитируется сокрытие атрибутов в главе 24, в разделе с описанием метода __getattr__. Несмотря на то, что в языке Python вполне возможно организовать управление доступом к атрибутам классов, тем не менее, этот прием редко ис
    пользуется на практике, даже в крупных программах.
    Классы нового стиля
    В версии Python 2.2 появилась новая разновидность классов, извест
    ная как классы «нового стиля». Классы, рассматривавшиеся до сих пор в этой части книги, называют «классическими классами», когда сравнивают их с новой разновидностью.
    Классы нового стиля лишь немного отличаются от классических клас
    сов, и эти отличия совершенно незаметны для подавляющего большин
    ства пользователей Python. Кроме того, классическая модель классов,
    используемая в языке Python на протяжении последних 15 лет, по
    прежнему работает именно так, как было описано выше.

    Классы нового стиля
    669
    Классы нового стиля практически сохраняют обратную совместимость с классическими классами в синтаксисе и в поведении – они привно
    сят лишь несколько новых особенностей. Однако, т.к. они изменили один особый случай наследования, их следует представлять как от
    дельный инструмент, чтобы избежать нежелательных воздействий на любой существующий программный код, работоспособность которого зависит от прежнего поведения классов.
    Классы нового стиля создаются с помощью привычной синтаксиче
    ской конструкции, которую мы уже изучили. Главное отличие состоит в том, что новые классы объявляются как подклассы встроенных ти
    пов (например, list). Если ни один из встроенных типов не подходит для наследования, тогда новые классы в качестве суперкласса должны указывать новое встроенное имя object:
    class newstyle(object):
    ...обычная реализация класса...
    Если говорить более широко, любой класс, наследующий object или любой другой встроенный тип, автоматически будет рассматриваться как класс нового стиля. (Под наследованием я подразумеваю все под
    классы object, подклассы подклассов object, и т. д. – если гдето в дере
    ве наследования в качестве суперкласса присутствует встроенный тип,
    класснаследник будет рассматриваться как класс нового стиля.) Клас
    сы, не наследующие встроенные типы, считаются классическими.
    Согласно утверждениям Гвидо ван Россума (Guido van Rossum),
    создателя языка Python, начиная с версии Python 3.0 все классы автоматически будут считаться классами нового стиля, поэтому требование наследования встроенных типов отпадет автоматиче
    ски. Даже самостоятельные классы будут считаться классами но
    вого стиля, к тому же классы нового стиля сохранят обратную со
    вместимость с классическими классами, поэтому для большинст
    ва программистов эти изменения будут малозаметны.
    В прошлом имелось некоторое беспокойство, что в Python 3.0
    классы верхнего уровня необходимо будет наследовать от object.
    Однако недавно Гвидо (Guido) заявил, что этого не потребуется.
    Для большинства программистов все классы в версии 3.0 будут ра
    ботать так, как описывается в этой книге, но с некоторыми новы
    ми особенностями, свойственными классам нового стиля. Тем не менее, я не берусь предсказывать будущее, поэтому за дополни
    тельной информацией обращайтесь к примечаниям к выпуску 3.0.
    Ромбоидальное наследование
    Пожалуй, самым ощутимым изменением в классах нового стиля явля
    ется немного отличная интерпретация наследования – так называемая
    ромбоидальная
    схема в деревьях множественного наследования, когда более одного суперкласса наследует один и тот же суперкласс более высокого уровня. Ромбоидальная схема – это сложная концепция про

    670
    Глава 26. Дополнительные возможности классов ектирования, которую мы даже не обсуждали при изучении обычных классов.
    Проще говоря, в классической модели процедура поиска в дереве насле
    дования сначала движется строго вверх по дереву, а потом слева напра
    во – сначала интерпретатор поднимается вверх всеми возможными пу
    тями по левой стороне дерева, затем возвращается назад и начинает по
    иск с первого суперкласса, расположенного правее предыдущего. В но
    вой модели в таких случаях поиск сначала производится в ширину –
    интерпретатор сначала просматривает все суперклассы, стоящие пра
    вее того, где поиск уже произведен, и только потом начинает подъем всеми возможными путями к общему суперклассу. Вследствие такого изменения суперклассы, расположенные ниже, получают возмож
    ность переопределять атрибуты суперклассов, стоящих выше, незави
    симо от вида деревьев множественного наследования.
    Пример ромбоидального наследования
    В качестве иллюстрации рассмотрим следующую упрощенную реали
    зацию ромбоидального наследования для классических классов:
    >>> class A: attr = 1 # Классическая модель
    >>> class B(A): pass
    >>> class C(A): attr = 2
    >>> class D(B,C): pass # Сначала поиск дойдет до A, потом до C
    >>> x = D()
    >>> x.attr
    1
    В этом случае атрибут attr будет найден в суперклассе A, потому что в классической модели поиск в дереве наследования сначала произво
    дится в высоту, и только потом происходит смещение вправо – интер
    претатор выполнит поиск в классах в следующем порядке: D, B, A и за
    тем C (впрочем, поиск прекратится, как только атрибут attr будет най
    ден в суперклассе A, расположенном выше суперкласса B). В классах нового стиля, наследующих встроенный тип, такой как object, прежде чем просмотреть суперкласс A, интерпретатор сначала выполнит поиск в суперклассе C (правее суперкласса B), то есть, в следующем порядке:
    D
    , B, C и затем A (но в этом случае поиск остановится в суперклассе C):
    >>> class A(object): attr = 1 # Новый стиль
    >>> class B(A): pass
    >>> class C(A): attr = 2
    >>> class D(B,C): pass # Сначала поиск дойдет до C, потом до A
    >>> x = D()
    >>> x.attr
    2
    Это изменение процедуры поиска основано на предположении, что ес
    ли вы добавляете класс C в дерево ниже, это значит, что вы хотите по
    лучить его атрибуты раньше, чем атрибуты класса A. Кроме того, это

    Классы нового стиля
    671
    изменение предполагает, что класс C всегда будет иметь возможность переопределить атрибуты класса A, что, скорее всего, верно, когда пи
    шется самостоятельный класс, но совсем неверно, когда в ромбоидаль
    ной схеме принимают участие классические классы – вы можете даже не подозревать, что класс C может участвовать в подобной схеме насле
    дования, когда пишете его.
    1   ...   77   78   79   80   81   82   83   84   ...   98


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