9. ООП в Python

Классы и объекты
Заголовок раздела «Классы и объекты»class BankAccount: # Атрибут класса (общий для всех) bank_name = "PyBank"
def __init__(self, owner: str, balance: float = 0): # Атрибуты экземпляра self.owner = owner self._balance = balance # _ = защищённый (соглашение) self.__secret = "hidden" # __ = приватный (name mangling)
def deposit(self, amount: float) -> None: if amount <= 0: raise ValueError("Сумма должна быть положительной") self._balance += amount
def withdraw(self, amount: float) -> float: if amount > self._balance: raise ValueError("Недостаточно средств") self._balance -= amount return amount
@property # геттер def balance(self) -> float: return self._balance
@balance.setter # сеттер def balance(self, value: float) -> None: if value < 0: raise ValueError("Баланс не может быть отрицательным") self._balance = value
def __repr__(self) -> str: # для разработчиков return f"BankAccount(owner='{self.owner}', balance={self._balance})"
def __str__(self) -> str: # для пользователей return f"Счёт {self.owner}: {self._balance:.2f}₽"
def __eq__(self, other) -> bool: return isinstance(other, BankAccount) and self.owner == other.owner
account = BankAccount("Яша", 1000)account.deposit(500)print(account.balance) # 1500.0print(account) # Счёт Яша: 1500.00₽print(repr(account)) # BankAccount(owner='Яша', balance=1500.0)Наследование
Заголовок раздела «Наследование»class Animal: def __init__(self, name: str, sound: str): self.name = name self.sound = sound
def speak(self) -> str: return f"{self.name} говорит: {self.sound}"
def __repr__(self): return f"{self.__class__.__name__}('{self.name}')"
class Dog(Animal): def __init__(self, name: str): super().__init__(name, "Гав!") # вызов родительского __init__
def fetch(self, item: str) -> str: return f"{self.name} принёс {item}"
class Cat(Animal): def __init__(self, name: str): super().__init__(name, "Мяу!")
def speak(self) -> str: # переопределение return f"{self.name} мурчит: {self.sound} (причём с достоинством)"
# Полиморфизмanimals: list[Animal] = [Dog("Шарик"), Cat("Мурка"), Dog("Бобик")]for animal in animals: print(animal.speak()) # каждый говорит по-своему
# Проверка типаisinstance(Dog("Шарик"), Animal) # Trueisinstance(Dog("Шарик"), Cat) # FalseАбстрактные классы
Заголовок раздела «Абстрактные классы»from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self) -> float: """Площадь фигуры.""" pass
@abstractmethod def perimeter(self) -> float: """Периметр фигуры.""" pass
def describe(self) -> str: return f"{self.__class__.__name__}: площадь={self.area():.2f}, периметр={self.perimeter():.2f}"
class Circle(Shape): def __init__(self, radius: float): self.radius = radius
def area(self) -> float: import math return math.pi * self.radius ** 2
def perimeter(self) -> float: import math return 2 * math.pi * self.radius
class Rectangle(Shape): def __init__(self, width: float, height: float): self.width = width self.height = height
def area(self) -> float: return self.width * self.height
def perimeter(self) -> float: return 2 * (self.width + self.height)
shapes: list[Shape] = [Circle(5), Rectangle(4, 6)]for shape in shapes: print(shape.describe())
# Shape() — ОШИБКА! Нельзя создать абстрактный классМиксины (Mixins)
Заголовок раздела «Миксины (Mixins)»class TimestampMixin: """Добавляет временные метки.""" def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs)
from datetime import datetime created_at: datetime = None updated_at: datetime = None
class SerializeMixin: """Сериализация в dict.""" def to_dict(self) -> dict: return { k: v for k, v in self.__dict__.items() if not k.startswith("_") }
@classmethod def from_dict(cls, data: dict): obj = cls.__new__(cls) for key, value in data.items(): setattr(obj, key, value) return obj
class User(TimestampMixin, SerializeMixin): def __init__(self, name: str, email: str): self.name = name self.email = email
user.to_dict() # {"name": "Яша", "email": "[email protected]"}Classmethod и Staticmethod
Заголовок раздела «Classmethod и Staticmethod»class Date: def __init__(self, year: int, month: int, day: int): self.year = year self.month = month self.day = day
@classmethod def from_string(cls, date_string: str) -> "Date": """Альтернативный конструктор.""" year, month, day = map(int, date_string.split("-")) return cls(year, month, day)
@classmethod def today(cls) -> "Date": from datetime import date d = date.today() return cls(d.year, d.month, d.day)
@staticmethod def is_valid(year: int, month: int, day: int) -> bool: """Не зависит от экземпляра.""" return 1 <= month <= 12 and 1 <= day <= 31
def __repr__(self): return f"Date({self.year}, {self.month}, {self.day})"
# Использованиеd1 = Date(2024, 1, 15)d2 = Date.from_string("2024-01-15") # classmethodd3 = Date.today()
Date.is_valid(2024, 13, 1) # False — staticmethodDunder методы (Magic methods)
Заголовок раздела «Dunder методы (Magic methods)»class Vector: def __init__(self, x: float, y: float): self.x = x self.y = y
def __repr__(self) -> str: return f"Vector({self.x}, {self.y})"
def __add__(self, other: "Vector") -> "Vector": return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar: float) -> "Vector": return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar: float) -> "Vector": return self.__mul__(scalar)
def __abs__(self) -> float: return (self.x**2 + self.y**2) ** 0.5
def __eq__(self, other) -> bool: return isinstance(other, Vector) and self.x == other.x and self.y == other.y
def __len__(self) -> int: return 2 # всегда 2D
v1 = Vector(1, 2)v2 = Vector(3, 4)v1 + v2 # Vector(4, 6)v1 * 3 # Vector(3, 6)3 * v1 # Vector(3, 6) — через __rmul__abs(v2) # 5.0Паттерн Repository (практический пример)
Заголовок раздела «Паттерн Repository (практический пример)»from abc import ABC, abstractmethodfrom dataclasses import dataclass, fieldfrom datetime import datetime
@dataclassclass User: name: str email: str id: int | None = None created_at: datetime = field(default_factory=datetime.now)
class UserRepository(ABC): @abstractmethod def find_by_id(self, id: int) -> User | None: ...
@abstractmethod def find_all(self) -> list[User]: ...
@abstractmethod def save(self, user: User) -> User: ...
@abstractmethod def delete(self, id: int) -> bool: ...
class InMemoryUserRepository(UserRepository): def __init__(self): self._users: dict[int, User] = {} self._next_id = 1
def find_by_id(self, id: int) -> User | None: return self._users.get(id)
def find_all(self) -> list[User]: return list(self._users.values())
def save(self, user: User) -> User: if user.id is None: user.id = self._next_id self._next_id += 1 self._users[user.id] = user return user
def delete(self, id: int) -> bool: if id in self._users: del self._users[id] return True return False
# Использованиеrepo = InMemoryUserRepository()print(user.id) # 1Задание
Заголовок раздела «Задание»# Задание 1: Интернет-магазин# Создай иерархию классов:# - Product (базовый): name, price, stock# - PhysicalProduct: weight, dimensions# - DigitalProduct: download_url, file_size# Добавь метод __str__, is_available(), apply_discount()
# Задание 2: Стек с историей# Реализуй класс HistoryStack:# - push(item): добавить элемент# - pop(): удалить и вернуть последний# - undo(): отменить последнее действие# - peek(): посмотреть верхний элемент# - __len__(), __repr__()В следующем уроке — модули и пакеты!