""" Урок 78. Магические методы — весь код примеров одним файлом. Источник: subjects/python-fundamentals/course/lessons/78-magic-methods/examples.html Файл собран автоматически (tools/build_lesson_examples.py): правьте страницу урока. Запуск: python lesson-78.py """ from __future__ import annotations # ==================================================================== # Пример 1. Сравнение книг # ==================================================================== class Book: def __init__(self, title: str) -> None: self.title = title def __eq__(self, other: object) -> bool: if not isinstance(other, Book): return NotImplemented return self.title == other.title def __lt__(self, other: Book) -> bool: if not isinstance(other, Book): return NotImplemented return self.title < other.title def __repr__(self) -> str: return f"Book(title={self.title!r})" b1 = Book("1984") b2 = Book("1984") b3 = Book("Brave New World") print(b1 == b2) # True print(b1 < b3) # True print(sorted([b3, b1, b2])) # ==================================================================== # Пример 2. Автоматический порядок через @total_ordering # ==================================================================== from functools import total_ordering @total_ordering class Book: def __init__(self, title: str) -> None: self.title = title def __eq__(self, other: object) -> bool: if not isinstance(other, Book): return NotImplemented return self.title == other.title def __lt__(self, other: Book) -> bool: return self.title < other.title b1 = Book("1984") b2 = Book("Brave New World") print(b1 < b2) # ручная реализация print(b1 >= b2) # автоматически print(b1 == b1) # ручная реализация print(b1 != b2) # автоматически # ==================================================================== # Пример 3. Коллекция заметок # ==================================================================== class Notes: def __init__(self) -> None: self._data: dict[str, str] = {} def __getitem__(self, key: str) -> str: return self._data[key.lower()] def __setitem__(self, key: str, value: str) -> None: if not value.strip(): raise ValueError("Empty notes are not allowed") self._data[key.lower()] = value def __contains__(self, key: str) -> bool: return key.lower() in self._data def __len__(self) -> int: return len(self._data) def __str__(self) -> str: lines = (f"- {k}: {v}" for k, v in self._data.items()) return "Notes:\n" + "\n".join(lines) notes = Notes() notes["Idea"] = "Build a game" notes["TODO"] = "Finish the project" print(notes["idea"]) # Build a game print("todo" in notes) # True print(len(notes)) # 2 print(notes) # ==================================================================== # Пример 4. Сложение векторов # ==================================================================== class Vector: def __init__(self, x: float, y: float) -> None: self.x = x self.y = y def __add__(self, other: Vector) -> Vector: if not isinstance(other, Vector): return NotImplemented return Vector(self.x + other.x, self.y + other.y) def __str__(self) -> str: return f"({self.x}, {self.y})" a = Vector(2, 3) b = Vector(1, 4) c = a + b print(c) # (3, 7) print(a, b) # (2, 3) (1, 4) # ==================================================================== # Пример 5. Изменяемый счётчик # ==================================================================== class Counter: def __init__(self, value: int = 0) -> None: self.value = value def __iadd__(self, other: int) -> Counter: self.value += other return self def __str__(self) -> str: return f"Counter({self.value})" c = Counter(5) print(id(c)) c += 3 print(id(c)) # тот же id print(c) # Counter(8) # ==================================================================== # Пример 6. Логическое значение счётчика # ==================================================================== class Counter: def __init__(self, value: int) -> None: self.value = value def __bool__(self) -> bool: return self.value > 0 c1 = Counter(-5) c2 = Counter(3) print(bool(c1), bool(c2)) # False True if c1: print("Есть элементы") else: print("Пусто") # ==================================================================== # Пример 7. Объект-конвертер валют # ==================================================================== class CurrencyConverter: def __init__(self, rate: float) -> None: self.rate = rate def __call__(self, dollars: float) -> float: return dollars * self.rate euro = CurrencyConverter(0.88) print(euro(10)) # 8.8 print(euro(5.5)) # 4.84 # ==================================================================== # Пример 8. Сводный пример: абстракция, исключения и магические методы вместе # ==================================================================== from abc import ABC, abstractmethod class ProductError(Exception): """Базовая ошибка товара.""" class InvalidPriceError(ProductError): """Некорректная цена товара.""" class Product(ABC): """ Абстрактный товар. Attributes: name (str): Название товара. price (float): Цена товара. """ def __init__(self, name: str, price: float) -> None: if price < 0: raise InvalidPriceError("Цена не может быть отрицательной.") self.name = name self.price = price @abstractmethod def get_description(self) -> str: """Возвращает описание товара.""" def __str__(self) -> str: return f"{self.name}: {self.price:.2f} €" def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self.name!r}, price={self.price!r})" def __eq__(self, other) -> bool: if not isinstance(other, Product): return NotImplemented return self.name == other.name and self.price == other.price def __lt__(self, other) -> bool: if not isinstance(other, Product): return NotImplemented return self.price < other.price class Phone(Product): def __init__(self, name: str, price: float, memory: int) -> None: super().__init__(name, price) self.memory = memory def get_description(self) -> str: return f"Телефон {self.name}, память {self.memory} ГБ" class Laptop(Product): def __init__(self, name: str, price: float, ram: int) -> None: super().__init__(name, price) self.ram = ram def get_description(self) -> str: return f"Ноутбук {self.name}, оперативная память {self.ram} ГБ" class ShoppingCart: """Корзина товаров.""" def __init__(self) -> None: self._products: list[Product] = [] def add(self, product: Product) -> None: if not isinstance(product, Product): raise TypeError("В корзину можно добавлять только товары.") self._products.append(product) def total(self) -> float: return sum(product.price for product in self._products) def __len__(self) -> int: return len(self._products) def __iter__(self): return iter(self._products) def __getitem__(self, index: int) -> Product: return self._products[index] def __contains__(self, product: Product) -> bool: return product in self._products def __str__(self) -> str: return f"Корзина: {len(self)} товар(а), сумма {self.total():.2f} €" # ==================================================================== # Пример 8. Сводный пример: абстракция, исключения и магические методы вместе # ==================================================================== phone = Phone(name="Google Pixel", price=899, memory=256) laptop = Laptop(name="Lenovo ThinkPad", price=1400, ram=32) cart = ShoppingCart() cart.add(phone) cart.add(laptop) print(cart) # Корзина: 2 товар(а), сумма 2299.00 € print(len(cart)) # 2 → __len__ print(cart[0]) # Google Pixel: 899.00 € → __getitem__ print(phone in cart) # True → __contains__ print(phone < laptop) # True → __lt__ print(repr(phone)) # Phone(name='Google Pixel', price=899) for product in cart: # → __iter__ print(product.get_description()) try: Phone(name="Ошибка", price=-100, memory=128) except InvalidPriceError as error: print(f"Не удалось создать товар: {error}")