Перейти к содержимому

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.0
print(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) # True
isinstance(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() — ОШИБКА! Нельзя создать абстрактный класс
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 = User("Яша", "[email protected]")
user.to_dict() # {"name": "Яша", "email": "[email protected]"}
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") # classmethod
d3 = Date.today()
Date.is_valid(2024, 13, 1) # False — staticmethod
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
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class 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()
user = repo.save(User("Яша", "[email protected]"))
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__()

В следующем уроке — модули и пакеты!