Python: Курс продвинутого Программирования. Часть вторая

- -
- 100%
- +
ABC: Базовый класс для создания абстрактных классов.
@abstractmethod: Декоратор для обозначения абстрактных методов.
python
from abc import ABC, abstractmethod
# Абстрактный класс Shape – определяет, что такое «фигура» в контексте нашего приложения
class Shape (ABC):
def __init__ (self, name):
self.name = name
@abstractmethod # Обязательный метод для всех производных классов
def area (self):
pass
@abstractmethod # Обязательный метод для всех производных классов
def perimeter (self):
pass
def describe (self): # Обычный метод, который могут использовать подклассы
print (f"This is a shape named {self.name}.»)
# Попытка создать экземпляр абстрактного класса приведет к ошибке
# try:
# shape = Shape («Generic»)
# except TypeError as e:
# print (e) # Выведет: Can’t instantiate abstract class Shape with abstract methods area, perimeter
# Конкретный подкласс, реализующий абстрактные методы
class Circle (Shape):
def __init__ (self, name, radius):
super ().__init__ (name)
if radius <= 0:
raise ValueError («Radius must be positive.»)
self. radius = radius
def area (self): # Реализация абстрактного метода
return 3.14159 * (self. radius ** 2)
def perimeter (self): # Реализация абстрактного метода
return 2 * 3.14159 * self. radius
# Создаем экземпляр конкретного подкласса
my_circle = Circle («MyCircle», 5)
my_circle.describe () # Вызов унаследованного метода
print (f"Area: {my_circle.area ()}»)
print (f"Perimeter: {my_circle.perimeter ()}»)
Абстракция помогает создать четкий контракт для вашего кода, гарантируя, что все объекты, которые должны вести себя как «фигура», будут иметь методы area () и perimeter ().
2.3 Специальные Методы (Magic Methods / Dunder Methods)
Специальные методы, имена которых начинаются и заканчиваются двумя символами подчеркивания (например, __init__, __str__), позволяют нашим классам взаимодействовать со встроенными функциями и операторами Python. Они называются «dunder» (double underscore) методами.
2.3.1 Строковое представление: __str__ и __repr__
__str__ (self): Определяет, что выводится при использовании print () или str () для объекта. Должно возвращать «пользовательское» строковое представление.
__repr__ (self): Определяет «официальное» строковое представление объекта, которое должно быть однозначным и, по возможности, воспроизводимым (т.е., eval (repr (obj)) == obj). Часто используется для отладки. Если __str__ не определен, print () будет использовать __repr__.
python
class Point:
def __init__ (self, x, y):
self. x = x
self. y = y
def __str__ (self): # Более читаемое представление для пользователя
return f»({self. x}, {self. y})»
def __repr__ (self): # Более техническое представление для разработчика
return f"Point (x= {self. x}, y= {self. y})»
p = Point (10, 20)
print (p) # Использует __str__. Вывод: (10, 20)
print (str (p)) # Использует __str__. Вывод: (10, 20)
print (repr (p)) # Использует __repr__. Вывод: Point (x=10, y=20)
# В интерактивной сессии (REPL) без print () обычно вызывается __repr__
#>>> p
# Point (x=10, y=20)
2.3.2 Перегрузка операторов
Специальные методы позволяют перегружать стандартные операторы Python для работы с вашими объектами.
__add__ (self, other): Для оператора +.
__sub__ (self, other): Для оператора -.
__mul__ (self, other): Для оператора *.
__len__ (self): Для функции len ().
__getitem__ (self, key): Для доступа по индексу [].
__eq__ (self, other): Для оператора ==.
python
class Vector:
def __init__ (self, x, y):
self. x = x
self. y = y
def __str__ (self):
return f»({self. x}, {self. y})»
def __add__ (self, other): # Перегрузка оператора сложения +
if not isinstance (other, Vector): # Проверка типа входных данных
return NotImplemented # Указываем, что операция не поддерживается для данного типа
new_x = self. x + other. x
new_y = self. y + other. y
return Vector (new_x, new_y) # Возвращаем новый объект Vector
def __len__ (self): # Перегрузка len ()
# Можно вернуть, например, количество элементов или что-то другое
# Здесь вернем как бы «размер» вектора, но это скорее пример
return 2 # У нас всегда 2 компоненты
v1 = Vector (2, 3)
v2 = Vector (5, 1)
v3 = v1 + v2 # Используем перегруженный оператор +
print (f»{v1} + {v2} = {v3}») # Вывод: (2, 3) + (5, 1) = (7, 4)
print (f"Length of v1: {len (v1)}») # Используем перегруженный len (). Вывод: Length of v1: 2
# Проверка типа с помощью `isinstance`
if isinstance (v1, Vector):
print («v1 is a Vector object.»)
2.4 Абстрактные базовые классы (ABC) и @abstractmethod (Более подробно)
Мы кратко касались этого в контексте абстракции, но важно понять, как это работает на практике. Абстрактные классы служат для определения общего интерфейса, который должны реализовать все подклассы.
abc. ABC: Делает класс абстрактным.
@abstractmethod: Декоратор, который указывает, что метод является абстрактным и должен быть реализован в подклассах.
python
from abc import ABC, abstractmethod
class Shape (ABC): # Абстрактный класс
def __init__ (self, name):
self.name = name
@abstractmethod
def area (self):
«„„Возвращает площадь фигуры.““»
pass # Отсутствие реализации
@abstractmethod
def perimeter (self):
«„„Возвращает периметр фигуры.““»
pass # Отсутствие реализации
def describe (self): # Неабстрактный метод, который может быть унаследован
print (f"This is a shape named {self.name}.»)
class Square (Shape):
def __init__ (self, name, side):
super ().__init__ (name)
self.side = side
def area (self): # Реализация абстрактного метода
return self.side ** 2
def perimeter (self): # Реализация абстрактного метода
return 4 * self.side
class Rectangle (Shape):
def __init__ (self, name, width, height):
super ().__init__ (name)
self. width = width
self. height = height
def area (self): # Реализация абстрактного метода
return self. width * self. height
def perimeter (self): # Реализация абстрактного метода
return 2 * (self. width + self. height)
# Создаем объекты
my_square = Square («MySquare», 5)
my_rect = Rectangle («MyRectangle», 4, 6)
shapes = [my_square, my_rect]
for shape in shapes:
shape.describe () # Унаследованный метод
print (f» Area: {shape.area ()}»)
print (f» Perimeter: {shape.perimeter ()}»)
print (» -" * 10)
# Пример: Если подкласс не реализует все абстрактные методы
# class Triangle (Shape): # ОШИБКА, если не реализовать area () и perimeter ()
# def __init__ (self, name, base, height):
# super ().__init__ (name)
# self.base = base
# self. height = height
# def area (self): # Реализован только один метод
# return 0.5 * self.base * self. height
#
# try:
# t = Triangle («MyTriangle», 3, 4)
# except TypeError as e:
# print (e) # Can’t instantiate abstract class Triangle with abstract methods perimeter
2.5 Композиция и Агрегация (Composition vs. Aggregation)
Это два способа построения отношений «имеет» (has-a) между классами, отличающиеся от наследования («является» is-a).
Композиция: Одноклассник «состоит из» другого класса. Жизненный цикл зависимого объекта полностью контролируется родительским объектом. Если родительский объект уничтожается, зависимый объект тоже уничтожается.
python
class Engine:
def __init__ (self, horsepower):
self. horsepower = horsepower
print («Engine created.»)
def start (self):
print (f"Engine started with {self. horsepower} HP.»)
def __del__ (self): # Магический метод для финальной очистки, вызывается при удалении объекта
print («Engine destroyed.»)
class Car:
def __init__ (self, brand, model, horsepower):
self.brand = brand
self.model = model
# Car «имеет» Engine как свою часть (композиция)
self. engine = Engine (horsepower)
print (f"Car created: {self.brand} {self.model}»)
def start_car (self):
print (f"Starting the car…»)
self.engine.start ()
def __del__ (self):
print (f"Car {self.brand} {self.model} destroyed.»)
# self. engine будет уничтожен автоматически при уничтожении car
my_car = Car («Ford», «Focus», 150)
my_car.start_car ()
print (» -" * 10)
del my_car # Уничтожаем объект car
# Вывод:
# Engine created.
# Car created: Ford Focus
# Starting the car…
# Engine started with 150 HP.
# – — – — —
# Car Ford Focus destroyed.
# Engine destroyed. (Уничтожен Engine, потому что он был частью Car)
Агрегация: Класс содержит ссылку на объект другого класса, но эти объекты существуют независимо. Жизненный цикл зависимого объекта не связан с жизненным циклом родительского.
python
class Address:
def __init__ (self, street, city):
self.street = street
self.city = city
print (f"Address created: {self.street}, {self.city}»)
def __del__ (self):
print (f"Address destroyed: {self.street}, {self.city}»)
class Person:
def __init__ (self, name, address):
self.name = name
# Person «имеет» Address, но Address может существовать и отдельно (агрегация)
self.address = address
print (f"Person created: {self.name}»)
def __del__ (self):
print (f"Person destroyed: {self.name}»)
# Создаем Address отдельно
addr = Address («123 Main St», «Anytown»)
# Создаем Person, передавая Address
person = Person («Alice», addr)
print (» -" * 10)
print(f"{person.name} lives at {person.address.street}, {person.address.city}»)
# Вывод: Alice lives at 123 Main St, Anytown
# Уничтожаем person
del person
# Вывод:
# Address created: 123 Main St, Anytown
# Person created: Alice
# – — – — —
# Person destroyed: Alice
# Address destroyed: 123 Main St, Anytown (Address все еще существует, т.к. он не был частью person)
2.6 Статические методы (@staticmethod) и методы класса (@classmethod)
@staticmethod:
Не получает неявного первого аргумента (self или cls).
Является просто функцией, логически связанной с классом, но не работающей с состоянием ни класса, ни его экземпляров.
Может вызываться как через класс, так и через экземпляр.
python
class MathHelper:
@staticmethod
def add (a, b):
return a + b
print(MathHelper.add (5, 3)) # Вывод: 8
helper = MathHelper ()
print(helper.add (10, 2)) # Вывод: 12
@classmethod:
Получает неявный первый аргумент cls – ссылку на сам класс.
Чаще всего используется для создания альтернативных конструкторов или для работы с атрибутами класса.
python
class Employee:
raise_percent = 1.10 # Атрибут класса
def __init__ (self, name, salary):
self.name = name
self.salary = salary
@classmethod
def from_string (cls, emp_str): # Альтернативный конструктор
# emp_str = «John-50000»
name, salary_str = emp_str. split (» -»)
salary = int (salary_str)
return cls (name, salary) # cls – это сам класс Employee
@classmethod
def get_raise_amount (cls): # Метод, работающий с атрибутом класса
return cls. raise_percent
emp_data = «Jane-60000»
emp1 = Employee.from_string (emp_data) # Создаем объект через метод класса
print(f"{emp1.name}’s salary: {emp1.salary}») # Вывод: Jane’s salary: 60000
print (f"Raise amount: {Employee.get_raise_amount ()}») # Вывод: Raise amount: 1.1
Резюме главы:
Сегодня мы углубили наше понимание ООП, рассмотрев:
Полиморфизм: Способность объектов разных классов реагировать на один и тот же вызов метода по-разному, включая «утиную типизацию» и переопределение методов.
Абстракцию: Использование абстрактных классов (abc, @abstractmethod) для определения интерфейсов и контрактов.
Специальные Методы: Магические методы (__str__, __repr__, __add__, __len__ и др.) для настройки поведения объектов и перегрузки операторов.
Композицию и Агрегацию: Способы построения отношений «имеет» между классами.
Статические Методы (@staticmethod) и Методы Класса (@classmethod): Различия и сферы применения.
Эти концепции позволяют создавать более гибкий, расширяемый и мощный код.
Глава 3: Продвинутые Приемы работы с Данными – Генераторы и Декораторы
В этой главе мы рассмотрим два мощных механизма Python, которые позволяют писать более эффективный и лаконичный код: генераторы и декораторы.
3.1 Генераторы (Generators)
Генераторы – это особый тип итераторов, которые создаются с помощью функций-генераторов (использующих yield) или выражений-генераторов (аналогичных списковым выражениям, но в круглых скобках). Они позволяют создавать последовательности элементов «на лету», не храня всю последовательность в памяти одновременно. Это делает их идеальными для работы с большими объемами данных.
3.1.1 Функции-генераторы (yield)
Вместо return функция-генератор использует ключевое слово yield для возврата значения. При каждом вызове yield функция приостанавливает свое выполнение, запоминая свое состояние, и возвращает указанное значение. При следующем запросе значения (например, в цикле for) выполнение функции возобновляется с того места, где оно было приостановлено.
python
def count_up_to (n):
«„„Генерирует числа от 0 до n-1.““»
i = 0
while i print (f» -> Yielding {i}») yield i i += 1 print (f» -> Resumed at {i}») # Создаем объект генератора counter = count_up_to (5) print («Starting iteration…») # Итерируемся по генератору for number in counter: print (f"Received: {number}») print («Iteration finished.») # Пример вывода: # Starting iteration… # -> Yielding 0 # Received: 0 # -> Resumed at 1 # -> Yielding 1 # Received: 1 # -> Resumed at 2 # -> Yielding 2 # Received: 2
Конец ознакомительного фрагмента.
Текст предоставлен ООО «Литрес».
Прочитайте эту книгу целиком, купив полную легальную версию на Литрес.
Безопасно оплатить книгу можно банковской картой Visa, MasterCard, Maestro, со счета мобильного телефона, с платежного терминала, в салоне МТС или Связной, через PayPal, WebMoney, Яндекс.Деньги, QIWI Кошелек, бонусными картами или другим удобным Вам способом.