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

  • Инструкция class

  • Экземпляры класса

  • Правила видимости

  • Наследование

  • MostEvilAccount.__mro__

  • справочник по Python. мм isbn 9785932861578 9 785932 861578


    Скачать 4.21 Mb.
    Названиемм isbn 9785932861578 9 785932 861578
    Анкорсправочник по Python
    Дата08.05.2022
    Размер4.21 Mb.
    Формат файлаpdf
    Имя файлаBizli_Python-Podrobnyy-spravochnik.440222.pdf
    ТипСправочник
    #518195
    страница13 из 82
    1   ...   9   10   11   12   13   14   15   16   ...   82
    Глава
    7
    .
    Классы и объектно-ориентированное
    программирование
    Классы – это механизм создания новых типов объектов. Эта глава подроб- но рассказывает о классах, но в ней не следует видеть всеохватывающее руководство по объектно-ориентированному программированию и про- ектированию. Здесь предполагается, что читатель уже имеет некото- рый опыт работы со структурами данных и владеет приемами объектно- ориентированного программирования в других языках программирова- ния, таких как C или Java. (Дополнительные сведения о терминологии и внутренней реализации объектов приводятся в главе 3 «Типы данных и объекты».)
    Инструкция class
    Класс
    определяет набор атрибутов, ассоциированных с ним, и используе- мых коллекцией объектов, которые называются экземплярами. Обычно класс – это коллекция функций (известных, как методы), переменных (из- вестных, как переменные класса) и вычисляемых атрибутов (известных, как свойства).
    Класс объявляется с помощью инструкции class. Тело класса составляет последовательность инструкций, которые выполняются на этапе определе- ния класса. Например:
    class Account(object):
    num_accounts = 0
    def __init__(self,name,balance):
    self.name = name self.balance = balance
    Account.num_accounts += 1
    def __del__(self):
    Account.num_accounts -= 1
    def deposit(self,amt):
    self.balance = self.balance + amt

    Экземпляры класса
    159
    def withdraw(self,amt):
    self.balance = self.balance - amt def inquiry(self):
    return self.balance
    Значения, создаваемые при выполнении тела класса, помещаются в объ- ект класса, который играет роль пространства имен, во многом подобно модулю. Например, ниже показано, как осуществляется доступ к членам класса Account:
    Account.num_accounts
    Account.__init__
    Account.__del__
    Account.deposit
    Account.withdraw
    Account.inquiry
    Важно отметить, что сама по себе инструкция class не создает никаких эк- земпляров класса (например, в предыдущем примере не создается ника- ких объектов типа Accounts). Вместо этого выполняется подготовка атрибу- тов, общих для всех экземпляров, которые будут созданы позднее. В этом смысле определение класса можно представить как шаблон.
    Функции, определяемые внутри класса, называются методами экземпля-
    ров
    . Метод экземпляра – это функция, оперирующая экземпляром класса, который передается ей в первом аргументе. В соответствии с соглашения- ми этому аргументу дается имя self, хотя точно так же можно использовать любое другое имя. Функции deposit(), withdraw() и inquiry(), определяемые предыдущем примере, – это методы экземпляра.
    Переменные класса, такие как num_accounts, – это значения, которые со- вместно используются всеми экземплярами класса (то есть они не могут со- держать отдельные значения для каждого из экземпляров). В данном слу- чае переменная хранит количество созданных экземпляров класса Account.
    Экземпляры класса
    Экземпляры класса создаются обращением к объекту класса, как к функ- ции. В результате этого создается новый экземпляр, который затем пере- дается методу __init__() класса. В число аргументов, передаваемых методу
    __init__()
    , входят: вновь созданный экземпляр self и дополнительные ар- гументы, указанные при вызове объекта класса. Например:
    # Создать новый счет a = Account(“Гвидо”, 1000.00) # Вызовет Account.__init__(a,”Гвидо”,1000.00)
    b = Account(“Билл”, 10.00)
    Внутри метода __init__() создаются атрибуты экземпляра путем присваи- вания значений атрибутам объекта self. Например, инструкция self.name
    = name создаст атрибут name экземпляра. После того как вновь созданный экземпляр будет возвращен пользователю, к его атрибутам, как и к атри- бутам класса, можно будет обратиться с помощью оператора точки (.), как показано ниже:

    160
    Глава 7. Классы и объектно-ориентированное программирование a.deposit(100.00) # Вызовет Account.deposit(a,100.00)
    b.withdraw(50.00) # Вызовет Account.withdraw(b,50.00)
    name = a.name # Получить имя владельца счета
    Оператор точки (.) отвечает за доступ к атрибутам. При обращении к атри- буту полученное значение может поступать из нескольких мест. Например, выражение a.name в предыдущем примере вернет значение атрибута name экземпляра a. Но выражение a.deposit вернет значение атрибута deposit
    (метод) класса Account. При обращении к атрибуту поиск требуемого име- ни сначала производится в экземпляре, и если он не увенчается успехом, поиск выполняется в классе экземпляра. Это дает классам возможность обеспечить совместное использование своих атрибутов всеми его экземпля- рами.
    Правила видимости
    Классы определяют собственные пространства имен, но они не образуют области видимости для имен, используемых внутри методов. То есть при реализации классов ссылки на атрибуты и методы должны быть полно- стью квалифицированы. Например, ссылки на атрибуты в методах всег- да должны производиться относительно имени self. По этой причине для примера выше следует использовать ссылку self.balance, а не balance. То же относится и к вызовам методов из других методов, как показано в следую- щем примере:
    class Foo(object):
    def bar(self):
    print(“bar!”)
    def spam(self):
    bar(self) # Ошибка! Обращение к ‘bar’ приведет к исключению NameError self.bar() # Правильно
    Foo.bar(self) # Тоже правильно
    Отсутствие собственной области видимости в методах классов – это одна из особенностей, отличающих Python от C++ или Java. Для тех, кому раньше приходилось работать с этими языками программирования, отмечу, что аргумент self в языке Python – это то же самое, что указатель this. Необ- ходимость явного использования self обусловлена тем, что язык Python не предоставляет средств явного объявления переменных, аналогичных, на- пример, int x или float y в языке C. Без этого невозможно определить, что подразумевает операция присваивания в методе, – сохранение значения в локальной переменной или в атрибуте экземпляра. Явное использование имени self устраняет эту неоднозначность – все значения, сохраняемые с помощью self, становятся частью экземпляра, а все остальные операции присваивания действуют с локальными переменными.
    Наследование
    Наследование
    – это механизм создания новых классов, призванный на- строить или изменить поведение существующего класса. Оригинальный класс называют базовым классом или суперклассом. Новый класс назы-

    Наследование
    161
    вают производным классом или подклассом. Когда новый класс создается с использованием механизма наследования, он «наследует» атрибуты базо- вых классов. Однако производный класс может переопределить любой из этих атрибутов и добавить новые атрибуты.
    Наследование определяется перечислением в инструкции class имен ба- зовых классов через запятые. В случае отсутствия подходящего базово- го класса определяется наследование класса object, как было показано в преды дущих примерах. object – это класс, который является родоначаль- ником всех объектов в языке Python и предоставляет реализацию по умол-
    Python и предоставляет реализацию по умол- и предоставляет реализацию по умол- чанию некоторых общих методов, таких как __str__(), создающий строко- вое представление объекта для вывода инструкцией print.
    Наследование часто используется для переопределения поведения суще- ствующих методов. Например, ниже приводится специализированная вер- сия класса Account, где переопределяется метод inquiry(), который время от времени будет возвращать значение баланса, превышающее фактическое значение, – в надежде, что невнимательный клиент превысит сумму счета и будет подвергнут большому штрафу при последующем платеже по воз- никшему кредиту:
    import random class EvilAccount(Account):
    def inquiry(self):
    if random.randint(0,4) == 1:
    return self.balance * 1.10 # Внимание: патентуем идею else:
    return self.balance
    ёё
    c = EvilAccount(“Джордж”, 1000.00)
    c.deposit(10.0) # Вызов Account.deposit(c,10.0)
    available = c.inquiry() # Вызов EvilAccount.inquiry(c)
    В этом примере экземпляры класса EvilAccount будут идентичны экземпля- рам класса Account, за исключением переопределенного метода inquiry().
    Поддержка механизма наследования в языке Python реализована за счет незначительного расширения оператора точки (.). А именно, если поиск атрибута в экземпляре или в классе экземпляра не увенчался успехом, он продолжается в базовом классе. Этот процесс продолжается, пока не оста- нется не просмотренных базовых классов. Это объясняет, почему вызов c.deposit()
    в предыдущем примере приводит к вызову метода deposit(), объ- явленного в классе Account.
    Подкласс может добавлять к экземплярам новые атрибуты, определяя соб- ственную версию метода __init__(). Например, следующая версия класса
    EvilAccount добавляет новый атрибут evilfactor:
    class EvilAccount(Account):
    def __init__(self,name,balance,evilfactor):
    Account.__init__(self,name,balance) # Вызов метода инициализации
    # базового класса Account self.evilfactor = evilfactor def inquiry(self):
    if random.randint(0,4) == 1:

    162
    Глава 7. Классы и объектно-ориентированное программирование return self.balance * self.evilfactor else:
    return self.balance
    Когда производный класс определяет собственный метод __init__(), методы
    __init__()
    базовых классов перестают вызываться автоматически. Поэтому производный класс должен следить за выполнением инициализации базо- вых классов, вызывая их методы __init__(). В предыдущем примере этот прием можно наблюдать в строке, где вызывается метод Account.__init__().
    Если базовый класс не имеет своего метода __init__(), этот шаг может быть пропущен. Если заранее не известно, определяется ли в базовом классе ме- тод __init__(), для надежности можно вызвать его без аргументов, потому что в конечном итоге всегда существует реализация по умолчанию, кото- рая просто ничего не делает.
    Иногда в производном классе требуется переопределить метод, но при этом желательно вызвать оригинальную реализацию. В этом случае можно явно вызвать оригинальный метод базового класса, передав ему экземпляр self в первом аргументе, как показано ниже:
    class MoreEvilAccount(EvilAccount):
    def deposit(self,amount):
    self.withdraw(5.00) # Вычесть плату за “удобство”
    EvilAccount.deposit(self,amount) # А теперь пополнить счет
    Главная хитрость в этом примере состоит в том, что класс EvilAccount в дей- ствительности не реализует метод deposit(). Вместо него вызывается метод класса Account. И хотя этот программный код работает, у тех, кто будет чи- тать его, могут возникнуть вопросы (например, обязан ли был класс Evil-
    Account реализовать метод deposit()). Чтобы избежать подобной путаницы, можно использовать функцию super(), как показано ниже:
    class MoreEvilAccount(EvilAccount):
    def deposit(self,amount):
    self.withdraw(5.00) # Вычесть плату за удобство super(MoreEvilAccount,self).deposit(amount)# А теперь пополнить счет
    Функция super(cls, instance) возвращает специальный объект, позволяю- щий выполнять поиск атрибутов в базовых классах. При использовании этой функции интерпретатор будет искать атрибуты, следуя обычным пра- вилам поиска в базовых классах. Это позволяет избежать жесткой при- вязки к имени базового класса и более ясно говорит о намерениях (то есть вы готовы вызвать предыдущую реализацию метода, независимо от того, в каком базовом классе она находится). К сожалению, синтаксис функции super()
    оставляет желать лучшего. В Python 3 можно использовать упро-
    Python 3 можно использовать упро-
    3 можно использовать упро- щенную инструкцию super().deposit(amount), чтобы выполнить необходи- мые вычисления, показанные в примере. Однако в Python 2 необходимо использовать более многословную версию.
    В языке Python поддерживается множественное наследование. Это дости- гается за счет указания нескольких базовых классов. Рассмотрим следую- щую коллекцию классов:

    Наследование
    163
    class DepositCharge(object):
    fee = 5.00
    def deposit_fee(self):
    self.withdraw(self.fee)
    ёё
    class WithdrawCharge(object):
    fee = 2.50
    def withdraw_fee(self):
    self.withdraw(self.fee)
    ёё
    # Класс, использующий механизм множественного наследования class MostEvilAccount(EvilAccount, DepositCharge, WithdrawCharge):
    def deposit(self,amt):
    self.deposit_fee()
    super(MostEvilAccount,self).deposit(amt)
    def withdraw(self,amt):
    self.withdraw_fee()
    super(MostEvilAcount,self).withdraw(amt)
    При использовании множественного наследования порядок поиска атрибу- тов становится более сложным, потому что появляется несколько возмож- ных путей поиска. Следующие инструкции иллюстрируют эту сложность:
    d = MostEvilAccount(“Dave”,500.00,1.10)
    d.deposit_fee() # Вызовет DepositCharge.deposit_fee(). fee == 5.00
    d.withdraw_fee() # Вызовет WithdrawCharge.withdraw_fee(). fee == 5.00 ??
    В этом примере методы deposit_fee() и withdraw_fee() имеют уникальные имена и обнаруживаются в соответствующих базовых классах. Однако соз- дается ощущение, что метод withdraw_fee() работает с ошибкой, потому что он не использует значение атрибута fee, инициализированного в его классе.
    Это обусловлено тем, что атрибут fee – это переменная класса, объявленная в двух различных базовых классах. В работе используется одно из этих зна- чений, но какое? (Подсказка: это значение атрибута DepositCharge.fee.)
    Чтобы обеспечить поиск атрибутов при множественном наследовании, все базовые классы включаются в список, в порядке от «более специали- зированных» к «менее специализированным». Затем, когда производится поиск, интерпретатор просматривает этот список, пока не найдет первое определение атрибута.
    В примере выше класс EvilAccount является более специализированным, чем класс Account, потому что он наследует класс Account. То же относит- ся и к классу MostEvilAccount, DepositCharge считается более специализиро- ванным, чем WithdrawCharge, потому что он стоит первым в списке базовых классов. Порядок поиска в базовых классах можно увидеть, если вывести содержимое атрибута __mro__ класса. Например:
    >>> MostEvilAccount.__mro__
    (,
    ,
    ,
    ,
    ,

    164
    Глава 7. Классы и объектно-ориентированное программирование
    )
    >>>
    В большинстве случаев правила составления этого списка «интуитивно понятны». То есть производный класс всегда проверяется раньше его ба- зовых классов, а если класс имеет несколько родителей, они всегда будут проверяться в том порядке, в каком были перечислены в объявлении клас- са. Однако точный порядок просмотра базовых классов в действительности намного сложнее и его нельзя описать с помощью какого-либо «простого» алгоритма, такого как поиск «снизу-вверх» или «слева-направо». Для упо- рядочения используется алгоритм C3-линеаризации, описанный в доку- менте «A Monotonic Superclass Linearization for Dylan» (Монотонная ли-
    A Monotonic Superclass Linearization for Dylan» (Монотонная ли-
    Monotonic Superclass Linearization for Dylan» (Монотонная ли-
    Monotonic Superclass Linearization for Dylan» (Монотонная ли-
    Superclass Linearization for Dylan» (Монотонная ли-
    Superclass Linearization for Dylan» (Монотонная ли-
    Linearization for Dylan» (Монотонная ли-
    Linearization for Dylan» (Монотонная ли- for Dylan» (Монотонная ли- for Dylan» (Монотонная ли-
    Dylan» (Монотонная ли-
    Dylan» (Монотонная ли-
    » (Монотонная ли- неаризация суперкласса для языка Dylan) (авторы К. Баррет (K. Barrett) и другие, был представлен на конференции OOPSLA’96). Одна из малоза-
    OOPSLA’96). Одна из малоза-
    ’96). Одна из малоза- метных особенностей этого алгоритма состоит в том, что его реализация в языке Python препятствует созданию определенных иерархий классов с возбуждением исключения TypeError. Например:
    class X(object): pass class Y(X): pass class Z(X,Y): pass # TypeError.
    # Невозможно определить непротиворечивый порядок
    # разрешения имен методов
    В данном случае алгоритм разрешения имен методов препятствует созда- нию Z, потому что он не может определить осмысленный порядок поиска в базовых классах. Например, класс X находится в списке родительских классов перед классом Y, поэтому он должен просматриваться первым. Од- нако класс Y является более специализированным, потому что наследует класс X. Поэтому если первым будет проверяться класс X, это не позволит отыскать специализированные реализации методов в классе Y, унаследо- ванных от класса X. На практике такие ситуации должны возникать очень редко, и если возникают, это обычно свидетельствует о более серьезных ошибках, допущенных при проектировании программы.
    Как правило, в большинстве программ лучше стараться избегать мно- жественного наследования. Однако иногда множественное наследование используется для объявления так называемых классов-примесей. Класс- примесь, обычно определяющий набор методов, объявляется, чтобы потом
    «подмешивать» его в другие классы, с целью расширения их функциональ- ных возможностей (почти как макроопределение). Обычно предполагает- ся, что существуют другие методы, и методы в классах-примесях встраи- ваются поверх них. Эту возможность иллюстрируют классы DepositCharge и WithdrawCharge, объявленные в предыдущем примере. Эти классы добав- ляют новые методы, такие как deposit_fee(), в классы, включающие их в число базовых классов. Однако вам едва ли потребовалось бы, например, создавать экземпляры самого класса DepositCharge. В действительности эк- земпляр этого класса едва ли мог бы иметь практическую пользу (он об- ладает всего одним методом, который сам по себе не может даже работать правильно).

    Полиморфизм, или динамическое связывание и динамическая типизация
    165
    И еще одно последнее замечание: если в этом примере потребуется испра- вить проблему со ссылкой на атрибут fee, методы deposit_fee() и withdraw_
    fee()
    можно изменить так, чтобы они обращались к атрибуту напрямую, используя имя класса вместо ссылки self (например, DepositChange.fee).
    1   ...   9   10   11   12   13   14   15   16   ...   82


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