{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Лекция 7. Часть 1 - Полиморфизм и инкапсуляция\n",
    "\n",
    "Практические примеры и задания.\n",
    "\n",
    "**Как работать:** запускайте ячейки по порядку и смотрите на вывод. В ячейках с заданиями пишите свой код."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Класс и экземпляр\n",
    "\n",
    "Класс - описание структуры. Экземпляр - конкретный объект, созданный по классу."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: минимальный класс с атрибутами\n",
    "class BankAccount:\n",
    "    def __init__(self, owner, balance):\n",
    "        self.owner = owner\n",
    "        self.balance = balance\n",
    "\n",
    "\n",
    "acc = BankAccount(\"Олег\", 7500.0)\n",
    "print(acc.owner, acc.balance)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: метод меняет состояние объекта\n",
    "class BankAccount:\n",
    "    def __init__(self, owner, balance):\n",
    "        self.owner = owner\n",
    "        self.balance = balance\n",
    "\n",
    "    def deposit(self, amount):\n",
    "        self.balance += amount\n",
    "\n",
    "\n",
    "acc = BankAccount(\"Олег\", 7500.0)\n",
    "acc.deposit(2500.0)\n",
    "print(acc.balance)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 3: __str__ задаёт читаемый вид объекта\n",
    "class BankAccount:\n",
    "    def __init__(self, owner, balance):\n",
    "        self.owner = owner\n",
    "        self.balance = balance\n",
    "\n",
    "    def __str__(self):\n",
    "        return f\"Счёт {self.owner}: {self.balance} сум\"\n",
    "\n",
    "\n",
    "print(BankAccount(\"Олег\", 7500.0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 1\n",
    "\n",
    "Создайте класс `Card` с атрибутами `holder` и `number` и методом `__str__`, возвращающим строку вида `Карта Asel: 8600...`. Создайте объект и выведите его через `print()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Наследование и super()\n",
    "\n",
    "Наследник переиспользует код родителя. `super().__init__(...)` вызывает конструктор родителя."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: наследник получает методы родителя\n",
    "class Account:\n",
    "    def __init__(self, owner):\n",
    "        self.owner = owner\n",
    "\n",
    "    def info(self):\n",
    "        print(\"Владелец:\", self.owner)\n",
    "\n",
    "\n",
    "class SavingsAccount(Account):\n",
    "    pass\n",
    "\n",
    "\n",
    "SavingsAccount(\"Олег\").info()   # метод достался от Account"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: super() расширяет конструктор родителя\n",
    "class BasicCard:\n",
    "    def __init__(self, holder, number):\n",
    "        self.holder = holder\n",
    "        self.number = number\n",
    "\n",
    "\n",
    "class PremiumCard(BasicCard):\n",
    "    def __init__(self, holder, number, cashback):\n",
    "        super().__init__(holder, number)   # поля родителя\n",
    "        self.cashback = cashback           # своё поле\n",
    "\n",
    "\n",
    "vip = PremiumCard(\"Asel\", \"8600...\", 0.05)\n",
    "print(vip.holder, vip.cashback)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 3: наследник добавляет собственный метод\n",
    "class PremiumCard(BasicCard):\n",
    "    def __init__(self, holder, number, cashback):\n",
    "        super().__init__(holder, number)\n",
    "        self.cashback = cashback\n",
    "\n",
    "    def bonus(self, amount):\n",
    "        return amount * self.cashback\n",
    "\n",
    "\n",
    "vip = PremiumCard(\"Asel\", \"8600...\", 0.05)\n",
    "print(\"Кэшбэк:\", vip.bonus(100000))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 2\n",
    "\n",
    "На основе `BasicCard` создайте класс `CorporateCard` с дополнительным атрибутом `company`. В `__init__` сначала вызовите `super().__init__(...)`. Создайте объект и выведите `holder` и `company`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Полиморфизм: переопределение методов\n",
    "\n",
    "Класс-наследник задаёт свою версию метода с тем же именем. Вызывающий код не знает конкретный класс."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: потомок переопределяет метод родителя\n",
    "class PaymentGateway:\n",
    "    def process(self, amount):\n",
    "        print(\"Базовая обработка:\", amount)\n",
    "\n",
    "\n",
    "class UzCardGateway(PaymentGateway):\n",
    "    def process(self, amount):\n",
    "        print(\"UzCard списывает:\", amount)\n",
    "\n",
    "\n",
    "UzCardGateway().process(150000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: super() - сначала логика родителя, потом своя\n",
    "class UzCardGateway(PaymentGateway):\n",
    "    def process(self, amount):\n",
    "        super().process(amount)         # общая логика\n",
    "        print(\"UzCard: генерация QR\")   # своя логика\n",
    "\n",
    "\n",
    "UzCardGateway().process(150000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 3: один цикл - разные классы\n",
    "class HumoGateway(PaymentGateway):\n",
    "    def process(self, amount):\n",
    "        print(\"Humo списывает:\", amount)\n",
    "\n",
    "\n",
    "for gw in [UzCardGateway(), HumoGateway()]:\n",
    "    gw.process(50000)                   # вызов один, поведение разное"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 3\n",
    "\n",
    "Добавьте класс `MillyBankGateway` - наследник `PaymentGateway`. Переопределите `process`: вызовите `super()`, затем выведите `Milly Bank: онлайн-платёж {amount}`. Прогоните объект в цикле вместе с другими шлюзами."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Утиная типизация\n",
    "\n",
    "Наследование не нужно. Достаточно, чтобы у объекта был метод с нужным именем."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: два несвязанных класса с одним методом\n",
    "class ApplePay:\n",
    "    def charge(self, amount):\n",
    "        print(\"Apple Pay:\", amount)\n",
    "\n",
    "\n",
    "class GooglePay:\n",
    "    def charge(self, amount):\n",
    "        print(\"Google Pay:\", amount)\n",
    "\n",
    "\n",
    "def pay(service, amount):\n",
    "    service.charge(amount)              # важно лишь наличие charge()\n",
    "\n",
    "\n",
    "pay(ApplePay(), 50000)\n",
    "pay(GooglePay(), 50000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: в цикл попадают любые объекты с методом charge()\n",
    "for wallet in [ApplePay(), GooglePay()]:\n",
    "    wallet.charge(30000)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 3: если метода нет - ошибка во время выполнения\n",
    "class EmptyService:\n",
    "    pass\n",
    "\n",
    "\n",
    "try:\n",
    "    pay(EmptyService(), 1000)\n",
    "except AttributeError as e:\n",
    "    print(\"Ошибка:\", e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 4\n",
    "\n",
    "Создайте класс `QrPayService` с методом `charge(self, amount)`, который выводит `QR: оплата {amount}`. Класс ничего не наследует. Передайте объект в функцию `pay`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. MRO - порядок разрешения методов\n",
    "\n",
    "Когда метод есть у нескольких родителей, Python ищет его по списку `__mro__` слева направо."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: цепочка наследования и __mro__\n",
    "class A:\n",
    "    pass\n",
    "\n",
    "\n",
    "class B(A):\n",
    "    pass\n",
    "\n",
    "\n",
    "print([c.__name__ for c in B.__mro__])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: ромб - чей метод сработает\n",
    "class Base:\n",
    "    def check(self):\n",
    "        print(\"Base\")\n",
    "\n",
    "\n",
    "class Left(Base):\n",
    "    def check(self):\n",
    "        print(\"Left\")\n",
    "\n",
    "\n",
    "class Right(Base):\n",
    "    def check(self):\n",
    "        print(\"Right\")\n",
    "\n",
    "\n",
    "class Both(Left, Right):\n",
    "    pass\n",
    "\n",
    "\n",
    "Both().check()                          # первый по MRO - Left\n",
    "print([c.__name__ for c in Both.__mro__])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 5\n",
    "\n",
    "Поменяйте порядок родителей: `class Both(Right, Left)`. Что выведет `Both().check()`? Проверьте и объясните результат в комментарии."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Инкапсуляция: _ и __\n",
    "\n",
    "`_имя` - соглашение «внутреннее, не трогать снаружи». `__имя` - Name Mangling: имя меняется на `_Класс__имя`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: _имя - защищённый атрибут по соглашению\n",
    "class Config:\n",
    "    def __init__(self):\n",
    "        self._token = \"secret-123\"      # снаружи трогать не стоит\n",
    "\n",
    "\n",
    "cfg = Config()\n",
    "print(cfg._token)                       # технически доступно"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: __имя - Name Mangling скрывает атрибут\n",
    "class User:\n",
    "    def __init__(self, pin):\n",
    "        self.__pin = pin\n",
    "\n",
    "\n",
    "u = User(\"7721\")\n",
    "try:\n",
    "    print(u.__pin)                      # имени __pin не существует\n",
    "except AttributeError as e:\n",
    "    print(\"Ошибка:\", e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 3: настоящее имя после mangling\n",
    "print(u._User__pin)                     # обходной путь\n",
    "print(u.__dict__)                       # видно реальное имя атрибута"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 6\n",
    "\n",
    "Создайте класс `Account` с публичным атрибутом `owner` и приватным `__balance` (начальное значение 0). Проверьте через `try / except`, что обращение к `account.__balance` вызывает `AttributeError`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. @property и сеттер\n",
    "\n",
    "`@property` - чтение как атрибут, но через метод (без скобок). Сеттер проверяет значение перед записью."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: @property - геттер только для чтения\n",
    "class Contract:\n",
    "    def __init__(self, number):\n",
    "        self._number = number\n",
    "\n",
    "    @property\n",
    "    def number(self):\n",
    "        return self._number\n",
    "\n",
    "\n",
    "c = Contract(\"UZ-001\")\n",
    "print(c.number)                         # обращение без скобок"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: сеттер проверяет значение перед записью\n",
    "class CreditLimit:\n",
    "    def __init__(self, limit):\n",
    "        self._limit = limit\n",
    "\n",
    "    @property\n",
    "    def limit(self):\n",
    "        return self._limit\n",
    "\n",
    "    @limit.setter\n",
    "    def limit(self, value):\n",
    "        if value < 0:\n",
    "            raise ValueError(\"Лимит не может быть отрицательным\")\n",
    "        self._limit = value\n",
    "\n",
    "\n",
    "c = CreditLimit(500000)\n",
    "c.limit = 800000\n",
    "print(c.limit)\n",
    "try:\n",
    "    c.limit = -100\n",
    "except ValueError as e:\n",
    "    print(\"Ошибка:\", e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 3: вычисляемое свойство\n",
    "class Loan:\n",
    "    def __init__(self, principal, rate):\n",
    "        self.principal = principal\n",
    "        self.rate = rate\n",
    "\n",
    "    @property\n",
    "    def total(self):\n",
    "        return self.principal * (1 + self.rate)\n",
    "\n",
    "\n",
    "loan = Loan(1000000, 0.20)\n",
    "print(loan.total)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 7\n",
    "\n",
    "Создайте класс `Temperature`: значение храните в `_celsius`, `@property celsius` для чтения, сеттер `celsius`, который отклоняет значения ниже `-273.15` (`raise ValueError`). Проверьте на корректном и некорректном значении."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Атрибут класса и атрибут экземпляра\n",
    "\n",
    "Атрибут класса - один на всех экземпляров. Атрибут экземпляра (`self.x`) - у каждого объекта свой."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 1: атрибут класса общий для всех объектов\n",
    "class Loan:\n",
    "    base_rate = 0.20                    # атрибут класса\n",
    "\n",
    "    def __init__(self, client):\n",
    "        self.client = client            # атрибут экземпляра\n",
    "\n",
    "\n",
    "a = Loan(\"Тимур\")\n",
    "b = Loan(\"Елена\")\n",
    "print(a.base_rate, b.base_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 2: меняем атрибут класса - меняется у всех\n",
    "Loan.base_rate = 0.22\n",
    "print(a.base_rate, b.base_rate)         # 0.22 у обоих"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Пример 3: счётчик объектов на атрибуте класса\n",
    "class Player:\n",
    "    count = 0\n",
    "\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        Player.count += 1\n",
    "\n",
    "\n",
    "Player(\"A\")\n",
    "Player(\"B\")\n",
    "print(\"Создано объектов:\", Player.count)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Задание 8\n",
    "\n",
    "В классе `Loan` добавьте атрибут класса `count = 0` и увеличивайте `Loan.count` в `__init__`. Создайте 3 кредита и выведите `Loan.count` (должно быть 3)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ваш код здесь"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}