""" Урок 75. Summary session 19 — весь код примеров одним файлом. Источник: subjects/python-fundamentals/course/lessons/75-summary-session-19/examples.html Файл собран автоматически (tools/build_lesson_examples.py): правьте страницу урока. Запуск: python lesson-75.py """ # ==================================================================== # Пример 1. Множественное наследование: базовый синтаксис # ==================================================================== class Printable: def print_info(self) -> None: print("Печать информации...") class Savable: def save(self) -> None: print("Сохраняем в файл...") class Report(Printable, Savable): pass report = Report() report.print_info() report.save() # Печать информации... # Сохраняем в файл... # ==================================================================== # Пример 2. MRO: как Python ищет метод при конфликте имён # ==================================================================== class Vehicle: def info(self) -> None: print("Vehicle: базовый транспорт") class Car(Vehicle): pass class Boat: def info(self) -> None: print("Boat: передвигается по воде") class AmphibiousVehicle(Car, Boat): pass av = AmphibiousVehicle() av.info() # Vehicle: базовый транспорт print(AmphibiousVehicle.__mro__) # (, , # , , ) # ==================================================================== # Пример 3. super() в диамант-наследовании: кооперативный вызов # ==================================================================== class LivingBeing: def describe(self) -> None: print("Живое существо") class LandAnimal(LivingBeing): def describe(self) -> None: print("Наземное животное") super().describe() class WaterAnimal(LivingBeing): def describe(self) -> None: print("Водное животное") super().describe() class Platypus(LandAnimal, WaterAnimal): def describe(self) -> None: print("Утконос") super().describe() print(Platypus.__mro__) # (Platypus, LandAnimal, WaterAnimal, LivingBeing, object) p = Platypus() p.describe() # Утконос # Наземное животное # Водное животное # Живое существо # ==================================================================== # Пример 4. Миксины и hasattr() # ==================================================================== class AuthMixin: def login(self) -> None: if not hasattr(self, "username"): raise AttributeError("Не задан username") print(f"{self.username} вошёл в систему.") def logout(self) -> None: print("Пользователь вышел из системы.") class NotificationMixin: def send_email(self, message: str) -> None: if not hasattr(self, "email"): raise AttributeError("Не задан email") print(f"Отправка письма на {self.email}: {message}") class UserProfile(AuthMixin, NotificationMixin): def __init__(self, username: str, email: str) -> None: self.username = username self.email = email user = UserProfile("alice", "alice@example.com") user.login() user.send_email("Добро пожаловать!") user.logout() # alice вошёл в систему. # Отправка письма на alice@example.com: Добро пожаловать! # Пользователь вышел из системы. class Anonymous(NotificationMixin): pass anon = Anonymous() try: anon.send_email("Привет") except AttributeError as error: print("AttributeError:", error) # AttributeError: Не задан email # ==================================================================== # Пример 5. Композиция и агрегация в одном сравнении # ==================================================================== class Engine: def start(self) -> None: print("Двигатель завёлся") class Automobile: def __init__(self, model: str) -> None: self.model = model self.engine = Engine() # композиция: двигатель создаётся внутри def drive(self) -> None: print(f"{self.model} трогается с места") self.engine.start() class University: def __init__(self, name: str) -> None: self.name = name def get_info(self) -> None: print(f"Обучение проходит в университете: {self.name}") class Teacher: def __init__(self, name: str, university: University) -> None: self.name = name self.university = university # агрегация: университет передан извне def introduce(self) -> None: print(f"Преподаватель: {self.name}") self.university.get_info() car = Automobile("Tesla Model 3") car.drive() # Tesla Model 3 трогается с места # Двигатель завёлся uni = University("Tech University") teacher1 = Teacher("Anna", uni) teacher2 = Teacher("Dmitry", uni) teacher1.introduce() teacher2.introduce() print(teacher1.university is teacher2.university) # True # ==================================================================== # Пример 6. Уровни доступа и манглирование имён # ==================================================================== class Book: def __init__(self, title: str, author: str) -> None: self.title = title # публичный self._author = author # защищённый (соглашение) self.__notes: list[str] = [] # приватный (манглируется) def add_note(self, note: str) -> None: self.__notes.append(note) def show_notes(self) -> None: for note in self.__notes: print(f"- {note}") book = Book("1984", "George Orwell") print(book.title) # 1984 — публичный доступен свободно print(book._author) # George Orwell — доступ возможен, но нежелателен book.add_note("Первая глава прочитана") book.show_notes() # - Первая глава прочитана print(sorted(book.__dict__.keys())) # ['_Book__notes', '_author', 'title'] try: print(book.__notes) except AttributeError as error: print("AttributeError:", error) # AttributeError: 'Book' object has no attribute '__notes' print(book._Book__notes) # ['Первая глава прочитана'] — обход через манглированное имя # ==================================================================== # Пример 7. Геттеры и сеттеры до @property # ==================================================================== class CelsiusOldStyle: def __init__(self, value: float) -> None: self.__validate(value) self.__celsius = value def get_celsius(self) -> float: return self.__celsius def set_celsius(self, value: float) -> None: self.__validate(value) self.__celsius = value @staticmethod def __validate(value: float) -> None: if value < -273.15: raise ValueError("Температура не может быть ниже абсолютного нуля") temp = CelsiusOldStyle(20) print(temp.get_celsius()) # 20 temp.set_celsius(25) print(temp.get_celsius()) # 25 try: temp.set_celsius(-300) except ValueError as error: print("ValueError:", error) # ValueError: Температура не может быть ниже абсолютного нуля # ==================================================================== # Пример 8. Ловушка @property: проверка работает, только если через неё идёт __init__ # ==================================================================== class PersonUnsafe: def __init__(self, age: int) -> None: self.__age = age # проверка пропущена: минуя сеттер @property def age(self) -> int: return self.__age @age.setter def age(self, value: int) -> None: if value < 0: raise ValueError("Возраст не может быть отрицательным") self.__age = value class PersonSafe: def __init__(self, age: int) -> None: self.age = age # значение идёт через сеттер @property def age(self) -> int: return self.__age @age.setter def age(self, value: int) -> None: if value < 0: raise ValueError("Возраст не может быть отрицательным") self.__age = value bad = PersonUnsafe(-10) print(bad.age) # -10 — объект с некорректными данными try: PersonSafe(-10) except ValueError as error: print("ValueError:", error) # ValueError: Возраст не может быть отрицательным # ==================================================================== # Пример 9. Read-only свойство: только вычисление, без сеттера # ==================================================================== class Circle: def __init__(self, radius: float) -> None: if radius <= 0: raise ValueError("Радиус должен быть положительным") self.__radius = radius @property def radius(self) -> float: return self.__radius @property def area(self) -> float: return 3.14159 * self.__radius ** 2 circle = Circle(5) print(circle.radius) # 5 print(circle.area) # 78.53975 try: circle.radius = 10 except AttributeError as error: print("AttributeError:", error) # AttributeError: property 'radius' of 'Circle' object has no setter # ==================================================================== # Пример 10. Синтез: миксин + композиция + @property в одном классе # ==================================================================== class AuditMixin: def audit(self, message: str) -> None: if not hasattr(self, "owner"): raise AttributeError("AuditMixin требует атрибут owner") print(f"[AUDIT] {self.owner}: {message}") class TransactionLog: def __init__(self) -> None: self.entries: list[str] = [] def add(self, entry: str) -> None: self.entries.append(entry) class BankAccount(AuditMixin): def __init__(self, owner: str, balance: float = 0) -> None: self.owner = owner self.__balance = balance self.__log = TransactionLog() # композиция: лог не живёт без счёта def deposit(self, amount: float) -> None: if amount <= 0: raise ValueError("Сумма должна быть положительной") self.__balance += amount self.__log.add(f"+{amount}") self.audit(f"пополнение на {amount}") def withdraw(self, amount: float) -> None: if amount <= 0: raise ValueError("Сумма должна быть положительной") if amount > self.__balance: raise ValueError("Недостаточно средств") self.__balance -= amount self.__log.add(f"-{amount}") self.audit(f"снятие {amount}") @property def balance(self) -> float: return self.__balance @property def history(self) -> list[str]: return self.__log.entries.copy() account = BankAccount("Alice", 100) account.deposit(150) account.withdraw(100) print(account.balance) # 150 print(account.history) # ['+150', '-100'] # [AUDIT] Alice: пополнение на 150 # [AUDIT] Alice: снятие 100 # ==================================================================== # Пример 11. Дескриптор: одна проверка на несколько полей # ==================================================================== class PositiveNumber: def __set_name__(self, owner, name: str) -> None: self.private_name = "_" + name def __get__(self, instance, owner): if instance is None: return self return getattr(instance, self.private_name) def __set__(self, instance, value: float) -> None: if value < 0: raise ValueError("Значение не может быть отрицательным") setattr(instance, self.private_name, value) class Employee: age = PositiveNumber() salary = PositiveNumber() def __init__(self, name: str, age: int, salary: float = 0) -> None: self.name = name self.age = age self.salary = salary employee = Employee("Viktor", 48, 1000) print(employee.age, employee.salary) # 48 1000 print(sorted(employee.__dict__.keys())) # ['_age', '_salary', 'name'] employee.salary = 1500 print(employee.salary) # 1500 try: employee.age = -10 except ValueError as error: print("ValueError:", error) # ValueError: Значение не может быть отрицательным