ээдд. Прохоренок_Н_А__Дронов_В_А_Python_3_и_PyQt_5_Разработка_приложен. Николай Прохоренок Владимир Дронов
Скачать 7.92 Mb.
|
<Экземпляр класса>.<Имя метода>([<Параметры>]) Обратите внимание на то, что при вызове метода не нужно передавать ссылку на экземпляр класса в качестве параметра, как это делается в определении метода внутри класса. Ссылку на экземпляр класса интерпретатор передает автоматически. Обращение к атрибутам класса осуществляется аналогично: <Экземпляр класса>.<Имя атрибута> Определим класс MyClass с атрибутом x и методом print_x() , выводящим значение этого атрибута, а затем создадим экземпляр класса и вызовем метод (листинг 13.2). Листинг 13.2. Создание атрибута и метода class MyClass: def __init__(self): # Конструктор self.x = 10 # Атрибут экземпляра класса def print_x(self): # self — это ссылка на экземпляр класса print(self.x) # Выводим значение атрибута c = MyClass() # Создание экземпляра класса # Вызываем метод print_x() c.print_x() # self не указывается при вызове метода print(c.x) # К атрибуту можно обратиться непосредственно Для доступа к атрибутам и методам можно использовать и следующие функции: getattr() — возвращает значение атрибута по его названию, заданному в виде строки. С помощью этой функции можно сформировать имя атрибута динамически во время выполнения программы. Формат функции: getattr(<Объект>, <Атрибут>[, <Значение по умолчанию>]) Если указанный атрибут не найден, возбуждается исключение AttributeError . Чтобы избежать вывода сообщения об ошибке, в третьем параметре можно указать значение, которое будет возвращаться, если атрибут не существует; setattr() — задает значение атрибута. Название атрибута указывается в виде строки. Формат функции: setattr(<Объект>, <Атрибут>, <Значение>) 246 Часть I. Основы языка Python Вторым параметром функции setattr() можно передать имя несуществующего атрибу- та — в этом случае атрибут с указанным именем будет создан; delattr(<Объект>, <Атрибут>) — удаляет атрибут, чье название указано в виде строки; hasattr(<Объект>, <Атрибут>) — проверяет наличие указанного атрибута. Если атрибут существует, функция возвращает значение True Продемонстрируем работу функций на примере (листинг 13.3). Листинг 13.3. Функции getattr(), setattr() и hasattr() class MyClass: def __init__(self): self.x = 10 def get_x(self): return self.x c = MyClass() # Создаем экземпляр класса print(getattr(c, "x")) # Выведет: 10 print(getattr(c, "get_x")()) # Выведет: 10 print(getattr(c, "y", 0)) # Выведет: 0, т. к. атрибут не найден setattr(c, "y", 20) # Создаем атрибут y print(getattr(c, "y", 0)) # Выведет: 20 delattr(c, "y") # Удаляем атрибут y print(getattr(c, "y", 0)) # Выведет: 0, т. к. атрибут не найден print(hasattr(c, "x")) # Выведет: True print(hasattr(c, "y")) # Выведет: False Все атрибуты класса в языке Python являются открытыми (public), т. е. доступными для не- посредственного изменения как из самого класса, так и из других классов и из основного кода программы. Кроме того, атрибуты допускается создавать динамически после создания класса — можно создать как атрибут объекта класса, так и атрибут экземпляра класса. Рассмотрим это на примере (листинг 13.4). Листинг 13.4. Атрибуты объекта класса и экземпляра класса class MyClass: # Определяем пустой класс pass MyClass.x = 50 # Создаем атрибут объекта класса c1, c2 = MyClass(), MyClass() # Создаем два экземпляра класса c1.y = 10 # Создаем атрибут экземпляра класса c2.y = 20 # Создаем атрибут экземпляра класса print(c1.x, c1.y) # Выведет: 50 10 print(c2.x, c2.y) # Выведет: 50 20 В этом примере мы определяем пустой класс, разместив в нем оператор pass . Далее создаем атрибут объекта класса: x . Этот атрибут будет доступен всем создаваемым экземплярам класса. Затем создаем два экземпляра класса и добавляем одноименные атрибуты: y . Значе- ния этих атрибутов будут разными в каждом экземпляре класса. Но если создать новый эк- земпляр (например, c3 ), то атрибут y в нем определен не будет. Таким образом, с помощью Глава 13. Объектно-ориентированное программирование 247 классов можно имитировать типы данных, поддерживаемые другими языками программи- рования (например, тип struct , доступный в языке C). Очень важно понимать разницу между атрибутами объекта класса и атрибутами экземпляра класса. Атрибут объекта класса доступен всем экземплярам класса, и после изменения атрибута значение изменится во всех экземплярах класса. Атрибут экземпляра класса мо- жет хранить уникальное значение для каждого экземпляра, и изменение его в одном экзем- пляре класса не затронет значения одноименного атрибута в других экземплярах того же класса. Рассмотрим это на примере, создав класс с атрибутом объекта класса ( x ) и атрибу- том экземпляра класса ( y ): >>> class MyClass: x = 10 # Атрибут объекта класса def __init__(self): self.y = 20 # Атрибут экземпляра класса Теперь создадим два экземпляра этого класса: >>> c1 = MyClass() # Создаем экземпляр класса >>> c2 = MyClass() # Создаем экземпляр класса Выведем значения атрибута x , а затем изменим значение и опять произведем вывод: >>> print(c1.x, c2.x) # 10 10 >>> MyClass.x = 88 # Изменяем атрибут объекта класса >>> print(c1.x, c2.x) # 88 88 Как видно из примера, изменение атрибута объекта класса затронуло значение в двух экземплярах класса сразу. Теперь произведем аналогичную операцию с атрибутом y : >>> print(c1.y, c2.y) # 20 20 >>> c1.y = 88 # Изменяем атрибут экземпляра класса >>> print(c1.y, c2.y) # 88 20 В этом случае изменилось значение атрибута только в экземпляре c1 Следует также учитывать, что в одном классе могут одновременно существовать атрибут объекта и атрибут экземпляра с одним именем. Изменение атрибута объекта класса мы про- изводили следующим образом: >>> MyClass.x = 88 # Изменяем атрибут объекта класса Если после этой инструкции вставить инструкцию >>> c1.x = 200 # Создаем атрибут экземпляра то будет создан атрибут экземпляра класса, а не изменено значение атрибута объекта клас- са. Чтобы увидеть разницу, выведем значения атрибутов: >>> print(c1.x, MyClass.x) # 200 88 13.2. Методы __init__() и __del__() При создании экземпляра класса интерпретатор автоматически вызывает метод инициали- зации __init__() . В других языках программирования такой метод принято называть кон- структором класса. Формат метода: def __init__(self[, <Значение1>[, ..., <ЗначениеN>]]): <Инструкции> 248 Часть I. Основы языка Python С помощью метода __init__() атрибутам класса можно присвоить начальные значения. При создании экземпляра класса параметры этого метода указываются после имени класса в круглых скобках: <Экземпляр класса> = <Имя класса>([<Значение1>[, ..., <ЗначениеN>]]) Пример использования метода __init__() приведен в листинге 13.5. Листинг 13.5. Метод __init__() class MyClass: def __init__(self, value1, value2): # Конструктор self.x = value1 self.y = value2 c = MyClass(100, 300) # Создаем экземпляр класса print(c.x, c.y) # Выведет: 100 300 Если конструктор вызывается при создании экземпляра, то перед уничтожением экземпляра автоматически вызывается метод, называемый деструктором. В языке Python деструктор реализуется в виде предопределенного метода __del__() (листинг 13.6). Следует заметить, что метод не будет вызван, если на экземпляр класса существует хотя бы одна ссылка. Впрочем, поскольку интерпретатор самостоятельно заботится об удалении объектов, ис- пользование деструктора в языке Python не имеет особого смысла. Листинг 13.6. Метод __del__() class MyClass: def __init__(self): # Конструктор класса print("Вызван метод __init__()") def __del__(self): # Деструктор класса print("Вызван метод __del__()") c1 = MyClass() # Выведет: Вызван метод __init__() del c1 # Выведет: Вызван метод __del__() c2 = MyClass() # Выведет: Вызван метод __init__() c3 = c2 # Создаем ссылку на экземпляр класса del c2 # Ничего не выведет, т. к. существует ссылка del c3 # Выведет: Вызван метод __del__() 13.3. Наследование Наследование является, пожалуй, самым главным понятием ООП. Предположим, у нас есть класс (например, Сlass1 ). При помощи наследования мы можем создать новый класс (на- пример, Сlass2 ), в котором будет реализован доступ ко всем атрибутам и методам класса Сlass1 (листинг 13.7). Листинг 13.7. Наследование class Class1: # Базовый класс def func1(self): print("Метод func1() класса Class1") Глава 13. Объектно-ориентированное программирование 249 def func2(self): print("Метод func2() класса Class1") class Class2(Class1): # Класс Class2 наследует класс Class1 def func3(self): print("Метод func3() класса Class2") c = Class2() # Создаем экземпляр класса Class2 c.func1() # Выведет: Метод func1() класса Class1 c.func2() # Выведет: Метод func2() класса Class1 c.func3() # Выведет: Метод func3() класса Class2 Как видно из примера, класс Class1 указывается внутри круглых скобок в определении класса Class2 . Таким образом, класс Class2 наследует все атрибуты и методы класса Class1 Класс Class1 называется базовым или суперклассом, а класс Class2 — производным или подклассом. Если имя метода в классе Class2 совпадает с именем метода класса Class1 , то будет исполь- зоваться метод из класса Class2 . Чтобы вызвать одноименный метод из базового класса, перед методом следует через точку написать название базового класса, а в первом парамет- ре метода — явно указать ссылку на экземпляр класса. Рассмотрим это на примере (листинг 13.8). Листинг 13.8. Переопределение методов class Class1: # Базовый класс def __init__(self): print("Конструктор базового класса") def func1(self): print("Метод func1() класса Class1") class Class2(Class1): # Класс Class2 наследует класс Class1 def __init__(self): print("Конструктор производного класса") Class1.__init__(self) # Вызываем конструктор базового класса def func1(self): print("Метод func1() класса Class2") Class1.func1(self) # Вызываем метод базового класса c = Class2() # Создаем экземпляр класса Class2 c.func1() # Вызываем метод func1() Выведет: Конструктор производного класса Конструктор базового класса Метод func1() класса Class2 Метод func1() класса Class1 В НИМАНИЕ ! Конструктор базового класса автоматически не вызывается, если он переопределен в про- изводном классе. 250 Часть I. Основы языка Python Чтобы вызвать одноименный метод из базового класса, также можно воспользоваться функцией super() . Формат функции: super([<Класс>, <Указатель self>]) С помощью функции super() инструкцию Class1.__init__(self) # Вызываем конструктор базового класса можно записать так: super().__init__() # Вызываем конструктор базового класса или так: super(Class2, self).__init__() # Вызываем конструктор базового класса Обратите внимание на то, что при использовании функции super() не нужно явно переда- вать указатель self в вызываемый метод. Кроме того, в первом параметре функции super() указывается производный класс, а не базовый. Поиск идентификатора будет производиться во всех базовых классах. Результатом поиска станет первый найденный идентификатор в цепочке наследования. П РИМЕЧАНИЕ В последних версиях Python 2 существовало два типа классов: «классические» классы и классы нового стиля. Классы нового стиля должны были явно наследовать класс object. В Python 3 все классы являются классами нового стиля, но наследуют класс object неявно. Таким образом, все классы верхнего уровня являются наследниками этого класса, даже если он не указан в списке наследования. «Классические» классы (в понимании Python 2) в Python 3 больше не поддерживаются. 13.4. Множественное наследование В определении класса в круглых скобках можно указать сразу несколько базовых классов через запятую. Рассмотрим множественное наследование на примере (листинг 13.9). Листинг 13.9. Множественное наследование class Class1: # Базовый класс для класса Class2 def func1(self): print("Метод func1() класса Class1") class Class2(Class1): # Класс Class2 наследует класс Class1 def func2(self): print("Метод func2() класса Class2") class Class3(Class1): # Класс Class3 наследует класс Class1 def func1(self): print("Метод func1() класса Class3") def func2(self): print("Метод func2() класса Class3") def func3(self): print("Метод func3() класса Class3") def func4(self): print("Метод func4() класса Class3") Глава 13. Объектно-ориентированное программирование 251 class Class4(Class2, Class3): # Множественное наследование def func4(self): print("Метод func4() класса Class4") c = Class4() # Создаем экземпляр класса Class4 c.func1() # Выведет: Метод func1() класса Class3 c.func2() # Выведет: Метод func2() класса Class2 c.func3() # Выведет: Метод func3() класса Class3 c.func4() # Выведет: Метод func4() класса Class4 Метод func1() определен в двух классах: Class1 и Class3 . Так как вначале просматривают- ся все базовые классы, непосредственно указанные в определении текущего класса, метод func1() будет найден в классе Class3 (поскольку он указан в числе базовых классов в опре- делении Class4 ), а не в классе Class1 Метод func2() также определен в двух классах: Class2 и Class3 . Так как класс Class2 стоит первым в списке базовых классов, то метод будет найден именно в нем. Чтобы наследовать метод из класса Class3 , следует указать это явным образом. Переделаем определение класса Class4 из предыдущего примера и наследуем метод func2() из класса Class3 (лис- тинг 13.10). Листинг 13.10. Указание класса при наследовании метода class Class4(Class2, Class3): # Множественное наследование # Наследуем func2() из класса Class3, а не из класса Class2 func2 = Class3.func2 def func4(self): print("Метод func4() класса Class4") Вернемся к листингу 13.9. Метод func3() определен только в классе Class3 , поэтому метод наследуется от этого класса. Метод func4() , определенный в классе Class3 , переопределя- ется в производном классе. Если искомый метод найден в производном классе, то вся иерархия наследования просмат- риваться не будет. Для получения перечня базовых классов можно воспользоваться атрибутом __bases__ В качестве значения атрибут возвращает кортеж. В качестве примера выведем базовые классы для всех классов из предыдущего примера: >>> print(Class1.__bases__) >>> print(Class2.__bases__) >>> print(Class3.__bases__) >>> print(Class4.__bases__) Выведет: ( ( ( ( Рассмотрим порядок поиска идентификаторов при сложной иерархии множественного на- следования (листинг 13.11). 252 Часть I. Основы языка Python Листинг 13.11. Поиск идентификаторов при множественном наследовании class Class1: x = 10 class Class2(Class1): pass class Class3(Class2): pass class Class4(Class3): pass class Class5(Class2): pass class Class6(Class5): pass class Class7(Class4, Class6): pass c = Class7() print(c.x) Последовательность поиска атрибута x будет такой: Class7 -> Class4 -> Class3 — > Class6 -> Class5 -> Class2 -> Class1 Получить всю цепочку наследования позволяет атрибут __mro__ : >>> print(Class7.__mro__) Результат выполнения: ( 13.4.1. Примеси и их использование Множественное наследование, поддерживаемое Python, позволяет реализовать интересный способ расширения функциональности классов с помощью так называемых примесей (mixins). Примесь — это класс, включающий какие-либо атрибуты и методы, которые необ- ходимо добавить к другим классам. Объявляются они точно так же, как и обычные классы. В качестве примера объявим класс-примесь Mixin , после чего объявим еще два класса, до- бавим к их функциональности ту, что определена в примеси Mixin , и проверим ее в дейст- вии (листинг 13.12). Листинг 13.12. Расширение функциональности классов посредством примеси class Mixin: # Определяем сам класс-примесь attr = 0 # Определяем атрибут примеси def mixin_method(self): # Определяем метод примеси print("Метод примеси") class Class1 (Mixin): def method1(self): print("Метод класса Class1") class Class2 (Class1, Mixin): def method2(self): print("Метод класса Class2") Глава 13. Объектно-ориентированное программирование 253 c1 = Class1() c1.method1() c1.mixin_method() # Class1 поддерживает метод примеси c2 = Class2() c2.method1() c2.method2() c2.mixin_method() # Class2 также поддерживает метод примеси Вот результат выполнения кода, приведенного в листинге 13.12: Метод класса Class1 Метод примеси Метод класса Class1 Метод класса Class2 Метод примеси Примеси активно применяются в различных дополнительных библиотеках — в частности, в популярном веб-фреймворке Django. 13.5. Специальные методы Классы поддерживают следующие специальные методы: __call__() — позволяет обработать вызов экземпляра класса как вызов функции. Фор- мат метода: __call__(self[, <Параметр1>[, ..., <ПараметрN>]]) Пример: class MyClass: def __init__(self, m): self.msg = m def __call__(self): print(self.msg) c1 = MyClass("Значение1") # Создание экземпляра класса c2 = MyClass("Значение2") # Создание экземпляра класса c1() # Выведет: Значение1 c2() # Выведет: Значение2 __getattr__(self, <Атрибут>) — вызывается при обращении к несуществующему атри- буту класса: class MyClass: def __init__(self): self.i = 20 def __getattr__(self, attr): print("Вызван метод __getattr__()") return 0 c = MyClass() # Атрибут i существует print(c.i) # Выведет: 20. Метод __getattr__() не вызывается # Атрибут s не существует print(c.s) # Выведет: Вызван метод __getattr__() 0 |