ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 824
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Глава 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
1 ... 20 21 22 23 24 25 26 27 ... 83
254
Часть I. Основы языка Python
__getattribute__(self, <Атрибут>)
— вызывается при обращении к любому атрибуту класса. Необходимо учитывать, что использование точечной нотации (для обращения к атрибуту класса) внутри этого метода приведет к зацикливанию. Чтобы избежать за- цикливания, следует вызвать метод
__getattribute__()
объекта object и внутри этого метода вернуть значение атрибута или возбудить исключение
AttributeError
: class MyClass: def __init__(self): self.i = 20 def __getattribute__(self, attr): print("Вызван метод __getattribute__()") return object.__getattribute__(self, attr) # Только так!!! c = MyClass() print(c.i) # Выведет: Вызван метод __getattribute__() 20
__setattr__(self, <Атрибут>, <Значение>)
— вызывается при попытке присваивания значения атрибуту экземпляра класса. Если внутри метода необходимо присвоить зна- чение атрибуту, следует использовать словарь
__dict__
, поскольку при применении то- чечной нотации метод
__setattr__()
будет вызван повторно, что приведет к зациклива- нию: class MyClass: def __setattr__(self, attr, value): print("Вызван метод __setattr__()") self.__dict__[attr] = value # Только так!!! c = MyClass() c.i = 10 # Выведет: Вызван метод __setattr__() print(c.i) # Выведет: 10
__delattr__(self, <Атрибут>)
— вызывается при удалении атрибута с помощью инст- рукции del <Экземпляр класса>.<Атрибут>
;
__len__(self)
— вызывается при использовании функции len()
, а также для проверки объекта на логическое значение при отсутствии метода
__bool__()
. Метод должен воз- вращать положительное целое число: class MyClass: def __len__(self): return 50 c = MyClass() print(len(c)) # Выведет: 50
__bool__(self)
— вызывается при использовании функции bool()
;
__int__(self)
— вызывается при преобразовании объекта в целое число с помощью функции int()
;
__float__(self)
— вызывается при преобразовании объекта в вещественное число с по- мощью функции float()
;
__complex__(self)
— вызывается при преобразовании объекта в комплексное число с помощью функции complex()
;
__round__(self, n)
— вызывается при использовании функции round()
;
__index__(self)
— вызывается при использовании функций bin()
, hex()
и oct()
;
Глава 13. Объектно-ориентированное программирование
255
__repr__(self)
и
__str__(self)
— служат для преобразования объекта в строку. Метод
__repr__()
вызывается при выводе в интерактивной оболочке, а также при использова- нии функции repr()
. Метод
__str__()
вызывается при выводе с помощью функции print()
, а также при использовании функции str()
. Если метод
__str__()
отсутствует, будет вызван метод
__repr__()
. В качестве значения методы
__repr__()
и
__str__()
должны возвращать строку: class MyClass: def __init__(self, m): self.msg = m def __repr__(self): return "Вызван метод __repr__() {0}".format(self.msg) def __str__(self): return "Вызван метод __str__() {0}".format(self.msg) c = MyClass("Значение") print(repr(c)) # Выведет: Вызван метод __repr__() Значение print(str(c)) # Выведет: Вызван метод __str__() Значение print(c) # Выведет: Вызван метод __str__() Значение
__hash__(self)
— этот метод следует переопределить, если экземпляр класса планиру- ется использовать в качестве ключа словаря или внутри множества: class MyClass: def __init__(self, y): self.x = y def __hash__(self): return hash(self.x) c = MyClass(10) d = {} d[c] = "Значение" print(d[c]) # Выведет: Значение
Классы поддерживают еще несколько специальных методов, которые применяются лишь в особых случаях и будут рассмотрены в главе 15.
13.6. Перегрузка операторов
Перегрузка операторов позволяет экземплярам классов участвовать в обычных операциях.
Чтобы перегрузить оператор, необходимо в классе определить метод со специальным на- званием. Для перегрузки математических операторов используются следующие методы:
x + y
— сложение: x.__add__(y)
;
y + x
— сложение (экземпляр класса справа): x.__radd__(y)
;
x += y
— сложение и присваивание: x.__iadd__(y)
;
x — y
— вычитание: x.__sub__(y)
;
y — x
— вычитание (экземпляр класса справа): x.__rsub__(y)
;
x -= y
— вычитание и присваивание: x.__isub__(y)
;
x * y
— умножение: x.__mul__(y)
;
y * x
— умножение (экземпляр класса справа): x.__rmul__(y)
;
256
Часть I. Основы языка Python
x *= y
— умножение и присваивание: x.__imul__(y)
;
x / y
— деление: x.__truediv__(y)
;
y / x
— деление (экземпляр класса справа): x.__rtruediv__(y)
;
x /= y
— деление и присваивание: x.__itruediv__(y)
;
x // y
— деление с округлением вниз: x.__floordiv__(y)
;
y // x
— деление с округлением вниз (экземпляр класса справа): x.__rfloordiv__(y)
;
x //= y
— деление с округлением вниз и присваивание: x.__ifloordiv__(y)
;
x % y
— остаток от деления: x.__mod__(y)
;
y % x
— остаток от деления (экземпляр класса справа): x.__rmod__(y)
;
x %= y
— остаток от деления и присваивание: x.__imod__(y)
;
x ** y
— возведение в степень: x.__pow__(y)
;
y ** x
— возведение в степень (экземпляр класса справа): x.__rpow__(y)
;
x **= y
— возведение в степень и присваивание: x.__ipow__(y)
;
-x
— унарный минус: x.__neg__()
;
+x
— унарный плюс: x.__pos__()
;
abs(x)
— абсолютное значение: x.__abs__()
Пример перегрузки математических операторов приведен в листинге 13.13.
Листинг 13.13. Пример перегрузки математических операторов class MyClass: def __init__(self, y): self.x = y def __add__(self, y): # Перегрузка оператора + print("Экземпляр слева") return self.x + y def __radd__(self, y): # Перегрузка оператора + print("Экземпляр справа") return self.x + y def __iadd__(self, y): # Перегрузка оператора += print("Сложение с присваиванием") self.x += y return self c = MyClass(50) print(c + 10) # Выведет: Экземпляр слева 60 print(20 + c) # Выведет: Экземпляр справа 70 c += 30 # Выведет: Сложение с присваиванием print(c.x) # Выведет: 80
Методы перегрузки двоичных операторов:
x
— двоичная инверсия: x.__invert__()
;
x & y
— двоичное И: x.__and__(y)
;
Глава 13. Объектно-ориентированное программирование
257
y & x
— двоичное И (экземпляр класса справа): x.__rand__(y)
;
x &= y
— двоичное И и присваивание: x.__iand__(y)
;
x | y
— двоичное ИЛИ: x.__or__(y)
;
y | x
— двоичное ИЛИ (экземпляр класса справа): x.__ror__(y)
;
x |= y
— двоичное ИЛИ и присваивание: x.__ior__(y)
;
x ^ y
— двоичное исключающее ИЛИ: x.__xor__(y)
;
y ^ x
— двоичное исключающее ИЛИ (экземпляр класса справа): x.__rxor__(y)
;
x ^= y
— двоичное исключающее ИЛИ и присваивание: x.__ixor__(y)
;
x << y
— сдвиг влево: x.__lshift__(y)
;
y << x
— сдвиг влево (экземпляр класса справа): x.__rlshift__(y)
;
x <<= y
— сдвиг влево и присваивание: x.__ilshift__(y)
;
x >> y
— сдвиг вправо: x.__rshift__(y)
;
y >> x
— сдвиг вправо (экземпляр класса справа): x.__rrshift__(y)
;
x >>= y
— сдвиг вправо и присваивание: x.__irshift__(y)
Перегрузка операторов сравнения производится с помощью следующих методов:
x == y
— равно: x.__eq__(y)
;
x != y
— не равно: x.__ne__(y)
;
x < y
— меньше: x.__lt__(y)
;
x > y
— больше: x.__gt__(y)
;
x <= y
— меньше или равно: x.__le__(y)
;
x >= y
— больше или равно: x.__ge__(y)
;
y in x
— проверка на вхождение: x.__contains__(y)
Пример перегрузки операторов сравнения приведен в листинге 13.14.
Листинг 13.14. Пример перегрузки операторов сравнения class MyClass: def __init__(self): self.x = 50 self.arr = [1, 2, 3, 4, 5] def __eq__(self, y): # Перегрузка оператора == return self.x == y def __contains__(self, y): # Перегрузка оператора in return y in self.arr c = MyClass() print("Равно" if c == 50 else "Не равно") # Выведет: Равно print("Равно" if c == 51 else "Не равно") # Выведет: Не равно print("Есть" if 5 in c else "Нет") # Выведет: Есть
258
Часть I. Основы языка Python
13.7. Статические методы и методы класса
Внутри класса можно создать метод, который будет доступен без создания экземпляра класса (статический метод). Для этого перед определением метода внутри класса следует указать декоратор
@staticmethod
. Вызов статического метода без создания экземпляра клас- са осуществляется следующим образом:
<Название класса>.<Название метода>(<Параметры>)
Кроме того, можно вызвать статический метод через экземпляр класса:
<Экземпляр класса>.<Название метода>(<Параметры>)
Пример использования статических методов приведен в листинге 13.15.
Листинг 13.15. Статические методы class MyClass:
@staticmethod def func1(x, y): # Статический метод return x + y def func2(self, x, y): # Обычный метод в классе return x + y def func3(self, x, y): return MyClass.func1(x, y) # Вызов из метода класса print(MyClass.func1(10, 20)) # Вызываем статический метод c = MyClass() print(c.func2(15, 6)) # Вызываем метод класса print(c.func1(50, 12)) # Вызываем статический метод
# через экземпляр класса print(c.func3(23, 5)) # Вызываем статический метод
# внутри класса
Обратите внимание на то, что в определении статического метода нет параметра self
. Это означает, что внутри статического метода нет доступа к атрибутам и методам экземпляра класса.
Методы класса создаются с помощью декоратора
@classmethod
. В качестве первого пара- метра в метод класса передается ссылка на класс. Вызов метода класса осуществляется сле- дующим образом:
<Название класса>.<Название метода>(<Параметры>)
Кроме того, можно вызвать метод класса через экземпляр класса:
<Экземпляр класса>.<Название метода>(<Параметры>)
Пример использования методов класса приведен в листинге 13.16.
Листинг 13.16. Методы класса class MyClass:
@classmethod def func(cls, x): # Метод класса print(cls, x)
Глава 13. Объектно-ориентированное программирование
259
MyClass.func(10) # Вызываем метод через название класса c = MyClass() c.func(50) # Вызываем метод класса через экземпляр
13.8. Абстрактные методы
Абстрактные методы содержат только определение метода без реализации. Предполагает- ся, что производный класс должен переопределить метод и реализовать его функциональ- ность. Чтобы такое предположение сделать более очевидным, часто внутри абстрактного метода возбуждают исключение (листинг 13.17).
Листинг 13.17. Абстрактные методы class Class1: def func(self, x): # Абстрактный метод
# Возбуждаем исключение с помощью raise raise NotImplementedError("Необходимо переопределить метод") class Class2(Class1): # Наследуем абстрактный метод def func(self, x): # Переопределяем метод print(x) class Class3(Class1): # Класс не переопределяет метод pass c2 = Class2() c2.func(50) # Выведет: 50 c3 = Class3() try: # Перехватываем исключения c3.func(50) # Ошибка. Метод func() не переопределен except NotImplementedError as msg: print(msg) # Выведет: Необходимо переопределить метод
В состав стандартной библиотеки входит модуль abc
. В этом модуле определен декоратор
@abstractmethod
, который позволяет указать, что метод, перед которым расположен декора- тор, является абстрактным. При попытке создать экземпляр производного класса, в котором не переопределен абстрактный метод, возбуждается исключение
TypeError
. Рассмотрим использование декоратора
@abstractmethod на примере (листинг 13.18).
Листинг 13.18. Использование декоратора @abstractmethod from abc import ABCMeta, abstractmethod class Class1(metaclass=ABCMeta):
@abstractmethod def func(self, x): # Абстрактный метод pass class Class2(Class1): # Наследуем абстрактный метод def func(self, x): # Переопределяем метод print(x)
260
Часть I. Основы языка Python class Class3(Class1): # Класс не переопределяет метод pass c2 = Class2() c2.func(50) # Выведет: 50 try: c3 = Class3() # Ошибка. Метод func() не переопределен c3.func(50) except TypeError as msg: print(msg) # Can't instantiate abstract class Class3
# with abstract methods func
Имеется возможность создания абстрактных статических методов и абстрактных методов класса, для чего необходимые декораторы указываются одновременно, друг за другом. Для примера объявим класс с абстрактными статическим методом и методом класса (лис- тинг 13.19).
Листинг 13.19. Абстрактный статический метод и абстрактный метод класса from abc import ABCMeta, abstractmethod class MyClass(metaclass=ABCMeta):
@staticmethod
@abstractmethod def static_func(self, x): # Абстрактный статический метод pass
@classmethod
@abstractmethod def class_func(self, x): # Абстрактный метод класса pass
13.9. Ограничение доступа к идентификаторам внутри класса
Все идентификаторы внутри класса в языке Python являются открытыми, т. е. доступны для непосредственного изменения. Для имитации частных идентификаторов можно воспользо- ваться методами
__getattr__()
,
__getattribute__()
и
__setattr__()
, которые перехваты- вают обращения к атрибутам класса. Кроме того, можно воспользоваться идентификатора- ми, названия которых начинаются с двух символов подчеркивания. Такие идентификаторы называются псевдочастными. Псевдочастные идентификаторы доступны лишь внутри класса, но никак не вне его. Тем не менее изменить идентификатор через экземпляр класса все равно можно, зная, каким образом искажается название идентификатора. Напри- мер, идентификатор
__privateVar внутри класса
Class1
будет доступен по имени
_Class1__ privateVar
. Как можно видеть, здесь перед идентификатором добавляется название класса с предваряющим символом подчеркивания. Приведем пример использования псевдочаст- ных идентификаторов (листинг 13.20).
Глава 13. Объектно-ориентированное программирование
261
Листинг 13.20. Псевдочастные идентификаторы class MyClass: def __init__(self, x): self.__privateVar = x def set_var(self, x): # Изменение значения self.__privateVar = x def get_var(self): # Получение значения return self.__privateVar c = MyClass(10) # Создаем экземпляр класса print(c.get_var()) # Выведет: 10 c.set_var(20) # Изменяем значение print(c.get_var()) # Выведет: 20 try: # Перехватываем ошибки print(c.__privateVar) # Ошибка!!! except AttributeError as msg: print(msg) # Выведет: 'MyClass' object has
# no attribute '__privateVar' c._MyClass__privateVar = 50 # Значение псевдочастных атрибутов
# все равно можно изменить print(c.get_var()) # Выведет: 50
Можно также ограничить перечень атрибутов, разрешенных для экземпляров класса. Для этого разрешенные атрибуты указываются внутри класса в атрибуте
__slots__
. В качестве значения атрибуту можно присвоить строку или список строк с названиями идентификато- ров. Если производится попытка обращения к атрибуту, не указанному в
__slots__
, возбу- ждается исключение
AttributeError
(листинг 13.21).
Листинг 13.21. Использование атрибута __slots__ class MyClass:
__slots__ = ["x", "y"] def __init__(self, a, b): self.x, self.y = a, b c = MyClass(1, 2) print(c.x, c.y) # Выведет: 1 2 c.x, c.y = 10, 20 # Изменяем значения атрибутов print(c.x, c.y) # Выведет: 10 20 try: # Перехватываем исключения c.z = 50 # Атрибут z не указан в __slots__,
# поэтому возбуждается исключение except AttributeError as msg: print(msg) # 'MyClass' object has no attribute 'z'
13.10. Свойства класса
Внутри класса можно создать идентификатор, через который в дальнейшем будут произво- диться операции получения и изменения значения какого-либо атрибута, а также его удале- ния. Создается такой идентификатор с помощью функции property()
. Формат функции:
262
Часть I. Основы языка Python
<Свойство> = property(<Чтение>[, <Запись>[, <Удаление>
[, <Строка документирования>]]])
В первых трех параметрах указывается ссылка на соответствующий метод класса. При по- пытке получить значение будет вызван метод, указанный в первом параметре. При опера- ции присваивания значения будет вызван метод, указанный во втором параметре, — этот метод должен принимать один параметр. В случае удаления атрибута вызывается метод, указанный в третьем параметре. Если в качестве какого-либо параметра задано значение
None
, то это означает, что соответствующий метод не поддерживается. Рассмотрим свойства класса на примере (листинг 13.22).
Листинг 13.22. Свойства класса class MyClass: def __init__(self, value): self.__var = value def get_var(self): # Чтение return self.__var def set_var(self, value): # Запись self.__var = value def del_var(self): # Удаление del self.__var v = property(get_var, set_var, del_var, "Строка документирования") c = MyClass(5) c.v = 35 # Вызывается метод set_var() print(c.v) # Вызывается метод get_var() del c.v # Вызывается метод del_var()
Python поддерживает альтернативный метод определения свойств — с помощью методов getter()
, setter()
и deleter()
, которые используются в декораторах. Соответствующий пример приведен в листинге 13.23.
Листинг 13.23. Методы getter(), setter() и deleter() class MyClass: def __init__(self, value): self.__var = value
@property def v(self): # Чтение return self.__var
@v.setter def v(self, value): # Запись self.__var = value
@v.deleter def v(self): # Удаление del self.__var c = MyClass(5) c.v = 35 # Запись print(c.v) # Чтение del c.v # Удаление
Глава 13. Объектно-ориентированное программирование
263
Имеется возможность определить абстрактное свойство — в этом случае все реализующие его методы должны быть переопределены в подклассе. Выполняется это с помощью знако- мого нам декоратора
1 ... 21 22 23 24 25 26 27 28 ... 83
254
Часть I. Основы языка Python
__getattribute__(self, <Атрибут>)
— вызывается при обращении к любому атрибуту класса. Необходимо учитывать, что использование точечной нотации (для обращения к атрибуту класса) внутри этого метода приведет к зацикливанию. Чтобы избежать за- цикливания, следует вызвать метод
__getattribute__()
объекта object и внутри этого метода вернуть значение атрибута или возбудить исключение
AttributeError
: class MyClass: def __init__(self): self.i = 20 def __getattribute__(self, attr): print("Вызван метод __getattribute__()") return object.__getattribute__(self, attr) # Только так!!! c = MyClass() print(c.i) # Выведет: Вызван метод __getattribute__() 20
__setattr__(self, <Атрибут>, <Значение>)
— вызывается при попытке присваивания значения атрибуту экземпляра класса. Если внутри метода необходимо присвоить зна- чение атрибуту, следует использовать словарь
__dict__
, поскольку при применении то- чечной нотации метод
__setattr__()
будет вызван повторно, что приведет к зациклива- нию: class MyClass: def __setattr__(self, attr, value): print("Вызван метод __setattr__()") self.__dict__[attr] = value # Только так!!! c = MyClass() c.i = 10 # Выведет: Вызван метод __setattr__() print(c.i) # Выведет: 10
__delattr__(self, <Атрибут>)
— вызывается при удалении атрибута с помощью инст- рукции del <Экземпляр класса>.<Атрибут>
;
__len__(self)
— вызывается при использовании функции len()
, а также для проверки объекта на логическое значение при отсутствии метода
__bool__()
. Метод должен воз- вращать положительное целое число: class MyClass: def __len__(self): return 50 c = MyClass() print(len(c)) # Выведет: 50
__bool__(self)
— вызывается при использовании функции bool()
;
__int__(self)
— вызывается при преобразовании объекта в целое число с помощью функции int()
;
__float__(self)
— вызывается при преобразовании объекта в вещественное число с по- мощью функции float()
;
__complex__(self)
— вызывается при преобразовании объекта в комплексное число с помощью функции complex()
;
__round__(self, n)
— вызывается при использовании функции round()
;
__index__(self)
— вызывается при использовании функций bin()
, hex()
и oct()
;
Глава 13. Объектно-ориентированное программирование
255
__repr__(self)
и
__str__(self)
— служат для преобразования объекта в строку. Метод
__repr__()
вызывается при выводе в интерактивной оболочке, а также при использова- нии функции repr()
. Метод
__str__()
вызывается при выводе с помощью функции print()
, а также при использовании функции str()
. Если метод
__str__()
отсутствует, будет вызван метод
__repr__()
. В качестве значения методы
__repr__()
и
__str__()
должны возвращать строку: class MyClass: def __init__(self, m): self.msg = m def __repr__(self): return "Вызван метод __repr__() {0}".format(self.msg) def __str__(self): return "Вызван метод __str__() {0}".format(self.msg) c = MyClass("Значение") print(repr(c)) # Выведет: Вызван метод __repr__() Значение print(str(c)) # Выведет: Вызван метод __str__() Значение print(c) # Выведет: Вызван метод __str__() Значение
__hash__(self)
— этот метод следует переопределить, если экземпляр класса планиру- ется использовать в качестве ключа словаря или внутри множества: class MyClass: def __init__(self, y): self.x = y def __hash__(self): return hash(self.x) c = MyClass(10) d = {} d[c] = "Значение" print(d[c]) # Выведет: Значение
Классы поддерживают еще несколько специальных методов, которые применяются лишь в особых случаях и будут рассмотрены в главе 15.
13.6. Перегрузка операторов
Перегрузка операторов позволяет экземплярам классов участвовать в обычных операциях.
Чтобы перегрузить оператор, необходимо в классе определить метод со специальным на- званием. Для перегрузки математических операторов используются следующие методы:
x + y
— сложение: x.__add__(y)
;
y + x
— сложение (экземпляр класса справа): x.__radd__(y)
;
x += y
— сложение и присваивание: x.__iadd__(y)
;
x — y
— вычитание: x.__sub__(y)
;
y — x
— вычитание (экземпляр класса справа): x.__rsub__(y)
;
x -= y
— вычитание и присваивание: x.__isub__(y)
;
x * y
— умножение: x.__mul__(y)
;
y * x
— умножение (экземпляр класса справа): x.__rmul__(y)
;
256
Часть I. Основы языка Python
x *= y
— умножение и присваивание: x.__imul__(y)
;
x / y
— деление: x.__truediv__(y)
;
y / x
— деление (экземпляр класса справа): x.__rtruediv__(y)
;
x /= y
— деление и присваивание: x.__itruediv__(y)
;
x // y
— деление с округлением вниз: x.__floordiv__(y)
;
y // x
— деление с округлением вниз (экземпляр класса справа): x.__rfloordiv__(y)
;
x //= y
— деление с округлением вниз и присваивание: x.__ifloordiv__(y)
;
x % y
— остаток от деления: x.__mod__(y)
;
y % x
— остаток от деления (экземпляр класса справа): x.__rmod__(y)
;
x %= y
— остаток от деления и присваивание: x.__imod__(y)
;
x ** y
— возведение в степень: x.__pow__(y)
;
y ** x
— возведение в степень (экземпляр класса справа): x.__rpow__(y)
;
x **= y
— возведение в степень и присваивание: x.__ipow__(y)
;
-x
— унарный минус: x.__neg__()
;
+x
— унарный плюс: x.__pos__()
;
abs(x)
— абсолютное значение: x.__abs__()
Пример перегрузки математических операторов приведен в листинге 13.13.
Листинг 13.13. Пример перегрузки математических операторов class MyClass: def __init__(self, y): self.x = y def __add__(self, y): # Перегрузка оператора + print("Экземпляр слева") return self.x + y def __radd__(self, y): # Перегрузка оператора + print("Экземпляр справа") return self.x + y def __iadd__(self, y): # Перегрузка оператора += print("Сложение с присваиванием") self.x += y return self c = MyClass(50) print(c + 10) # Выведет: Экземпляр слева 60 print(20 + c) # Выведет: Экземпляр справа 70 c += 30 # Выведет: Сложение с присваиванием print(c.x) # Выведет: 80
Методы перегрузки двоичных операторов:
x
— двоичная инверсия: x.__invert__()
;
x & y
— двоичное И: x.__and__(y)
;
Глава 13. Объектно-ориентированное программирование
257
y & x
— двоичное И (экземпляр класса справа): x.__rand__(y)
;
x &= y
— двоичное И и присваивание: x.__iand__(y)
;
x | y
— двоичное ИЛИ: x.__or__(y)
;
y | x
— двоичное ИЛИ (экземпляр класса справа): x.__ror__(y)
;
x |= y
— двоичное ИЛИ и присваивание: x.__ior__(y)
;
x ^ y
— двоичное исключающее ИЛИ: x.__xor__(y)
;
y ^ x
— двоичное исключающее ИЛИ (экземпляр класса справа): x.__rxor__(y)
;
x ^= y
— двоичное исключающее ИЛИ и присваивание: x.__ixor__(y)
;
x << y
— сдвиг влево: x.__lshift__(y)
;
y << x
— сдвиг влево (экземпляр класса справа): x.__rlshift__(y)
;
x <<= y
— сдвиг влево и присваивание: x.__ilshift__(y)
;
x >> y
— сдвиг вправо: x.__rshift__(y)
;
y >> x
— сдвиг вправо (экземпляр класса справа): x.__rrshift__(y)
;
x >>= y
— сдвиг вправо и присваивание: x.__irshift__(y)
Перегрузка операторов сравнения производится с помощью следующих методов:
x == y
— равно: x.__eq__(y)
;
x != y
— не равно: x.__ne__(y)
;
x < y
— меньше: x.__lt__(y)
;
x > y
— больше: x.__gt__(y)
;
x <= y
— меньше или равно: x.__le__(y)
;
x >= y
— больше или равно: x.__ge__(y)
;
y in x
— проверка на вхождение: x.__contains__(y)
Пример перегрузки операторов сравнения приведен в листинге 13.14.
Листинг 13.14. Пример перегрузки операторов сравнения class MyClass: def __init__(self): self.x = 50 self.arr = [1, 2, 3, 4, 5] def __eq__(self, y): # Перегрузка оператора == return self.x == y def __contains__(self, y): # Перегрузка оператора in return y in self.arr c = MyClass() print("Равно" if c == 50 else "Не равно") # Выведет: Равно print("Равно" if c == 51 else "Не равно") # Выведет: Не равно print("Есть" if 5 in c else "Нет") # Выведет: Есть
258
Часть I. Основы языка Python
13.7. Статические методы и методы класса
Внутри класса можно создать метод, который будет доступен без создания экземпляра класса (статический метод). Для этого перед определением метода внутри класса следует указать декоратор
@staticmethod
. Вызов статического метода без создания экземпляра клас- са осуществляется следующим образом:
<Название класса>.<Название метода>(<Параметры>)
Кроме того, можно вызвать статический метод через экземпляр класса:
<Экземпляр класса>.<Название метода>(<Параметры>)
Пример использования статических методов приведен в листинге 13.15.
Листинг 13.15. Статические методы class MyClass:
@staticmethod def func1(x, y): # Статический метод return x + y def func2(self, x, y): # Обычный метод в классе return x + y def func3(self, x, y): return MyClass.func1(x, y) # Вызов из метода класса print(MyClass.func1(10, 20)) # Вызываем статический метод c = MyClass() print(c.func2(15, 6)) # Вызываем метод класса print(c.func1(50, 12)) # Вызываем статический метод
# через экземпляр класса print(c.func3(23, 5)) # Вызываем статический метод
# внутри класса
Обратите внимание на то, что в определении статического метода нет параметра self
. Это означает, что внутри статического метода нет доступа к атрибутам и методам экземпляра класса.
Методы класса создаются с помощью декоратора
@classmethod
. В качестве первого пара- метра в метод класса передается ссылка на класс. Вызов метода класса осуществляется сле- дующим образом:
<Название класса>.<Название метода>(<Параметры>)
Кроме того, можно вызвать метод класса через экземпляр класса:
<Экземпляр класса>.<Название метода>(<Параметры>)
Пример использования методов класса приведен в листинге 13.16.
Листинг 13.16. Методы класса class MyClass:
@classmethod def func(cls, x): # Метод класса print(cls, x)
Глава 13. Объектно-ориентированное программирование
259
MyClass.func(10) # Вызываем метод через название класса c = MyClass() c.func(50) # Вызываем метод класса через экземпляр
13.8. Абстрактные методы
Абстрактные методы содержат только определение метода без реализации. Предполагает- ся, что производный класс должен переопределить метод и реализовать его функциональ- ность. Чтобы такое предположение сделать более очевидным, часто внутри абстрактного метода возбуждают исключение (листинг 13.17).
Листинг 13.17. Абстрактные методы class Class1: def func(self, x): # Абстрактный метод
# Возбуждаем исключение с помощью raise raise NotImplementedError("Необходимо переопределить метод") class Class2(Class1): # Наследуем абстрактный метод def func(self, x): # Переопределяем метод print(x) class Class3(Class1): # Класс не переопределяет метод pass c2 = Class2() c2.func(50) # Выведет: 50 c3 = Class3() try: # Перехватываем исключения c3.func(50) # Ошибка. Метод func() не переопределен except NotImplementedError as msg: print(msg) # Выведет: Необходимо переопределить метод
В состав стандартной библиотеки входит модуль abc
. В этом модуле определен декоратор
@abstractmethod
, который позволяет указать, что метод, перед которым расположен декора- тор, является абстрактным. При попытке создать экземпляр производного класса, в котором не переопределен абстрактный метод, возбуждается исключение
TypeError
. Рассмотрим использование декоратора
@abstractmethod на примере (листинг 13.18).
Листинг 13.18. Использование декоратора @abstractmethod from abc import ABCMeta, abstractmethod class Class1(metaclass=ABCMeta):
@abstractmethod def func(self, x): # Абстрактный метод pass class Class2(Class1): # Наследуем абстрактный метод def func(self, x): # Переопределяем метод print(x)
260
Часть I. Основы языка Python class Class3(Class1): # Класс не переопределяет метод pass c2 = Class2() c2.func(50) # Выведет: 50 try: c3 = Class3() # Ошибка. Метод func() не переопределен c3.func(50) except TypeError as msg: print(msg) # Can't instantiate abstract class Class3
# with abstract methods func
Имеется возможность создания абстрактных статических методов и абстрактных методов класса, для чего необходимые декораторы указываются одновременно, друг за другом. Для примера объявим класс с абстрактными статическим методом и методом класса (лис- тинг 13.19).
Листинг 13.19. Абстрактный статический метод и абстрактный метод класса from abc import ABCMeta, abstractmethod class MyClass(metaclass=ABCMeta):
@staticmethod
@abstractmethod def static_func(self, x): # Абстрактный статический метод pass
@classmethod
@abstractmethod def class_func(self, x): # Абстрактный метод класса pass
13.9. Ограничение доступа к идентификаторам внутри класса
Все идентификаторы внутри класса в языке Python являются открытыми, т. е. доступны для непосредственного изменения. Для имитации частных идентификаторов можно воспользо- ваться методами
__getattr__()
,
__getattribute__()
и
__setattr__()
, которые перехваты- вают обращения к атрибутам класса. Кроме того, можно воспользоваться идентификатора- ми, названия которых начинаются с двух символов подчеркивания. Такие идентификаторы называются псевдочастными. Псевдочастные идентификаторы доступны лишь внутри класса, но никак не вне его. Тем не менее изменить идентификатор через экземпляр класса все равно можно, зная, каким образом искажается название идентификатора. Напри- мер, идентификатор
__privateVar внутри класса
Class1
будет доступен по имени
_Class1__ privateVar
. Как можно видеть, здесь перед идентификатором добавляется название класса с предваряющим символом подчеркивания. Приведем пример использования псевдочаст- ных идентификаторов (листинг 13.20).
Глава 13. Объектно-ориентированное программирование
261
Листинг 13.20. Псевдочастные идентификаторы class MyClass: def __init__(self, x): self.__privateVar = x def set_var(self, x): # Изменение значения self.__privateVar = x def get_var(self): # Получение значения return self.__privateVar c = MyClass(10) # Создаем экземпляр класса print(c.get_var()) # Выведет: 10 c.set_var(20) # Изменяем значение print(c.get_var()) # Выведет: 20 try: # Перехватываем ошибки print(c.__privateVar) # Ошибка!!! except AttributeError as msg: print(msg) # Выведет: 'MyClass' object has
# no attribute '__privateVar' c._MyClass__privateVar = 50 # Значение псевдочастных атрибутов
# все равно можно изменить print(c.get_var()) # Выведет: 50
Можно также ограничить перечень атрибутов, разрешенных для экземпляров класса. Для этого разрешенные атрибуты указываются внутри класса в атрибуте
__slots__
. В качестве значения атрибуту можно присвоить строку или список строк с названиями идентификато- ров. Если производится попытка обращения к атрибуту, не указанному в
__slots__
, возбу- ждается исключение
AttributeError
(листинг 13.21).
Листинг 13.21. Использование атрибута __slots__ class MyClass:
__slots__ = ["x", "y"] def __init__(self, a, b): self.x, self.y = a, b c = MyClass(1, 2) print(c.x, c.y) # Выведет: 1 2 c.x, c.y = 10, 20 # Изменяем значения атрибутов print(c.x, c.y) # Выведет: 10 20 try: # Перехватываем исключения c.z = 50 # Атрибут z не указан в __slots__,
# поэтому возбуждается исключение except AttributeError as msg: print(msg) # 'MyClass' object has no attribute 'z'
13.10. Свойства класса
Внутри класса можно создать идентификатор, через который в дальнейшем будут произво- диться операции получения и изменения значения какого-либо атрибута, а также его удале- ния. Создается такой идентификатор с помощью функции property()
. Формат функции:
262
Часть I. Основы языка Python
<Свойство> = property(<Чтение>[, <Запись>[, <Удаление>
[, <Строка документирования>]]])
В первых трех параметрах указывается ссылка на соответствующий метод класса. При по- пытке получить значение будет вызван метод, указанный в первом параметре. При опера- ции присваивания значения будет вызван метод, указанный во втором параметре, — этот метод должен принимать один параметр. В случае удаления атрибута вызывается метод, указанный в третьем параметре. Если в качестве какого-либо параметра задано значение
None
, то это означает, что соответствующий метод не поддерживается. Рассмотрим свойства класса на примере (листинг 13.22).
Листинг 13.22. Свойства класса class MyClass: def __init__(self, value): self.__var = value def get_var(self): # Чтение return self.__var def set_var(self, value): # Запись self.__var = value def del_var(self): # Удаление del self.__var v = property(get_var, set_var, del_var, "Строка документирования") c = MyClass(5) c.v = 35 # Вызывается метод set_var() print(c.v) # Вызывается метод get_var() del c.v # Вызывается метод del_var()
Python поддерживает альтернативный метод определения свойств — с помощью методов getter()
, setter()
и deleter()
, которые используются в декораторах. Соответствующий пример приведен в листинге 13.23.
Листинг 13.23. Методы getter(), setter() и deleter() class MyClass: def __init__(self, value): self.__var = value
@property def v(self): # Чтение return self.__var
@v.setter def v(self, value): # Запись self.__var = value
@v.deleter def v(self): # Удаление del self.__var c = MyClass(5) c.v = 35 # Запись print(c.v) # Чтение del c.v # Удаление
Глава 13. Объектно-ориентированное программирование
263
Имеется возможность определить абстрактное свойство — в этом случае все реализующие его методы должны быть переопределены в подклассе. Выполняется это с помощью знако- мого нам декоратора
1 ... 21 22 23 24 25 26 27 28 ... 83