""" Урок 73. Практикум 18 — весь код примеров одним файлом. Источник: subjects/python-fundamentals/course/lessons/73-practice-18/examples.html Файл собран автоматически (tools/build_lesson_examples.py): правьте страницу урока. Запуск: python lesson-73.py """ # ==================================================================== # Пример 1. Базовое наследование: super().__init__() и переопределение метода # ==================================================================== class Employee: def __init__(self, name: str, salary: float) -> None: self.name = name self.salary = salary def describe(self) -> str: return f"{self.name}: {self.salary}" class Manager(Employee): def __init__(self, name: str, salary: float, team_size: int) -> None: super().__init__(name, salary) # переиспользуем __init__ родителя self.team_size = team_size def describe(self) -> str: base = super().describe() # достраиваем базовую реализацию return f"{base}, team of {self.team_size}" employee = Employee("Alice", 50000.0) manager = Manager("Bob", 80000.0, 5) print(employee.describe()) # Alice: 50000.0 print(manager.describe()) # Bob: 80000.0, team of 5 # ==================================================================== # Пример 2. Валидация в конструкторе наследника # ==================================================================== class Receipt: def __init__(self, receipt_id: int, amount: float) -> None: self.id = receipt_id self.amount = amount def __str__(self) -> str: return f"{self.__class__.__name__} {self.id}: {self.amount}" class SaleReceipt(Receipt): def __init__(self, receipt_id: int, amount: float) -> None: if amount <= 0: raise ValueError("SaleReceipt amount must be positive.") super().__init__(receipt_id, amount) def __str__(self) -> str: return f"{self.__class__.__name__} {self.id}: +{self.amount}" class ReturnReceipt(Receipt): def __init__(self, receipt_id: int, amount: float) -> None: if amount >= 0: raise ValueError("ReturnReceipt amount must be negative.") super().__init__(receipt_id, amount) def __str__(self) -> str: return f"{self.__class__.__name__} {self.id}: {self.amount}" sale = SaleReceipt(1, 100.0) ret = ReturnReceipt(2, -50.0) print(sale) # SaleReceipt 1: +100.0 print(ret) # ReturnReceipt 2: -50.0 try: SaleReceipt(3, -10.0) except ValueError as error: print("ValueError:", error) # ValueError: SaleReceipt amount must be positive. # ==================================================================== # Пример 3. isinstance для фильтрации разнородных объектов # ==================================================================== class Shift: _id_counter = 1 def __init__(self) -> None: self.id = Shift._id_counter Shift._id_counter += 1 self.receipts: list[Receipt] = [] self.closed = False def close(self) -> None: self.closed = True def add_receipt(self, amount: float) -> None: if self.closed: raise ValueError("Cannot add receipt to closed shift.") receipt_id = len(self.receipts) + 1 self.receipts.append(SaleReceipt(receipt_id, amount)) def add_return(self, amount: float) -> None: if self.closed: raise ValueError("Cannot add receipt to closed shift.") receipt_id = len(self.receipts) + 1 self.receipts.append(ReturnReceipt(receipt_id, amount)) def get_total(self, receipt_type: type | tuple[type, ...] | None = None) -> float: if receipt_type is None: return sum(r.amount for r in self.receipts) return sum(r.amount for r in self.receipts if isinstance(r, receipt_type)) shift = Shift() shift.add_receipt(100.0) shift.add_receipt(250.0) shift.add_return(-40.0) print(shift.get_total()) # 310.0 print(shift.get_total(SaleReceipt)) # 350.0 print(shift.get_total(ReturnReceipt)) # -40.0 print(shift.get_total((SaleReceipt, ReturnReceipt))) # 310.0 — кортеж типов mixed = [SaleReceipt(1, 10.0), ReturnReceipt(2, -5.0), "not a receipt", 42] only_receipts = [item for item in mixed if isinstance(item, (SaleReceipt, ReturnReceipt))] print(len(only_receipts)) # 2 # ==================================================================== # Пример 4. hasattr — защитная проверка перед доступом к атрибуту # ==================================================================== class Config: def __init__(self, timeout: float) -> None: self.timeout = timeout def describe_config(config: Config) -> str: if not hasattr(config, "retries"): return f"timeout={config.timeout}, retries=default" return f"timeout={config.timeout}, retries={config.retries}" basic_config = Config(30.0) print(describe_config(basic_config)) # timeout=30.0, retries=default basic_config.retries = 3 print(describe_config(basic_config)) # timeout=30.0, retries=3 # ==================================================================== # Пример 5. Множественное наследование и порядок разрешения методов (MRO) # ==================================================================== class Base: def ping(self) -> str: return "Base.ping" class Left(Base): def ping(self) -> str: return "Left.ping -> " + super().ping() class Right(Base): def ping(self) -> str: return "Right.ping -> " + super().ping() class Diamond(Left, Right): pass print(Diamond.__mro__) # (, , , , ) print(Diamond().ping()) # Left.ping -> Right.ping -> Base.ping # ==================================================================== # Пример 6. Миксины: добавляем поведение через уведомления # ==================================================================== class EmailNotifyMixin: def email_notify(self, message: str) -> None: if not hasattr(self, "email"): raise AttributeError("Missing 'email'") print(f"Email to {self.email}: {message}") class SmsNotifyMixin: def sms_notify(self, message: str) -> None: if not hasattr(self, "phone"): raise AttributeError("Missing 'phone'") print(f"SMS to {self.phone}: {message}") class PushNotifyMixin: def push_notify(self, message: str) -> None: print(f"Push notification: {message}") class User: def __init__(self, name: str) -> None: self.name = name class Courier(User, EmailNotifyMixin, SmsNotifyMixin): def __init__(self, name: str, email: str, phone: str) -> None: super().__init__(name) self.email = email self.phone = phone def notify(self, message: str) -> None: self.email_notify(message) self.sms_notify(message) class Admin(User, PushNotifyMixin): def notify(self, message: str) -> None: self.push_notify(message) courier = Courier("Alice", "alice@example.com", "+123456789") courier.notify("Package delivered.") # Email to alice@example.com: Package delivered. # SMS to +123456789: Package delivered. admin = Admin("Bob") admin.notify("New report available.") # Push notification: New report available. # ==================================================================== # Пример 7. Ловушка миксина: обязательное поле в __init__ # ==================================================================== class BrokenAuditMixin: def __init__(self, actor: str) -> None: self.actor = actor def audit(self) -> None: print(f"Audited by {self.actor}") class Widget: def __init__(self, title: str) -> None: self.title = title # не вызывает super().__init__() — цепочка обрывается class BadPanel(Widget, BrokenAuditMixin): def __init__(self, title: str) -> None: super().__init__(title) # уходит в Widget.__init__, actor не передаётся bad_panel = BadPanel("Dashboard") try: bad_panel.audit() except AttributeError as error: print("AttributeError:", error) # AttributeError: 'BadPanel' object has no attribute 'actor' class GoodAuditMixin: def __init__(self, actor: str = "system") -> None: # значение по умолчанию super().__init__() self.actor = actor def audit(self) -> None: print(f"Audited by {self.actor}") class GoodPanel(GoodAuditMixin): def __init__(self, title: str) -> None: super().__init__() # actor получит значение по умолчанию "system" self.title = title good_panel = GoodPanel("Dashboard") good_panel.audit() # Audited by system print(good_panel.title) # Dashboard # ==================================================================== # Пример 8. Ловушка MRO: порядок родителей меняет вызываемый метод # ==================================================================== class LoudMixin: def greet(self) -> str: return "LOUD HELLO" class QuietMixin: def greet(self) -> str: return "quiet hello" class SpeakerA(LoudMixin, QuietMixin): pass class SpeakerB(QuietMixin, LoudMixin): pass print(SpeakerA.__mro__) # (..., , , ...) print(SpeakerA().greet()) # LOUD HELLO — первый по MRO родитель победил print(SpeakerB.__mro__) # (..., , , ...) print(SpeakerB().greet()) # quiet hello — родители поменяны местами # ==================================================================== # Пример 9. Всё вместе: смена, чеки и уведомления # ==================================================================== evening_shift = Shift() evening_shift.add_receipt(75.0) evening_shift.add_return(-15.0) evening_shift.close() print(evening_shift.get_total()) # 60.0 try: evening_shift.add_receipt(10.0) except ValueError as error: print("ValueError:", error) # ValueError: Cannot add receipt to closed shift. alice = Courier("Alice", "alice@example.com", "+123456789") alice.notify("Package delivered.") bob = Admin("Bob") bob.notify("New report available.")