{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bde8fdc5",
   "metadata": {},
   "source": [
    "# Методы #"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53f43d5b",
   "metadata": {},
   "source": [
    "Давайте усложним немножко наши примеры и добавим классам методы.\n",
    "\n",
    "Методы это по большому счёту просто функции, которые действуют в контексте экземпляра класса.\n",
    "\n",
    "Таким образом так как они действует в контексте экземпляра и получают ссылку на экземпляр класса, они могут менять его состояние, обращаясь к атрибутам экземпляра или делать любую полезную работу, которая нам захочется.\n",
    "\n",
    "Давайте посмотрим, как создать метод экземпляра и вызвать его. Население планеты нестабильно, люди рождаются и умирают поэтому мы создали класс `Human`, у которого есть два атрибута `name` и `age`, имя и возраст и также у нас есть класс планеты, у которой есть атрибут `name` и также атрибут `population`, который является списком и этот список будет содержать людей, которые есть на планете. Вот здесь мы объявили метод `add_human`, который является методом экземпляра и по большому счёту это просто функция, которая принимает первым аргументом `self`, это ссылка на экземпляр класса, а вторым аргументом всё что мы захотим."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "307c9d5a",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Human:\n",
    "    def __init__(self, name, age=0):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        \n",
    "        \n",
    "class Planet:\n",
    "    def __init__(self, name, population=None):\n",
    "        self.name = name\n",
    "        self.population = population or []\n",
    "        \n",
    "    def add_human(self, human):\n",
    "        print(f\"Welcome to {self.name}, {human.name}!\")\n",
    "        self.population.append(human)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba1164be",
   "metadata": {},
   "source": [
    "И так как это обычная функция, она может принимать сколько угодно аргументов как позиционных, так и именованных. В данном случае она принимает один. Это будет экземпляр класса `Human`. Внутри этого метода мы печатаем на экран приветственное сообщение новому человеку и обновляем атрибут экземпляра `population`. Добавляем туда нового человека.\n",
    "\n",
    "Также внутри методов, так как это просто функция в контексте экземпляра, мы можем использовать конструкцию `return`, чтобы возвращать из них какие-то значения, но в данном случае нам это не потребовалось. Давайте посмотрим как этим пользоваться, мы создаем экземпляр класса `Planet`, `Mars`, дальше мы создаем экземпляр класса `Human`, даем человеку имя Боб и затем мы у экземпляра класса `Planet Mars` вызываем метод `add_human` и передаем в него аргумент, объект нашего человека."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7a00536d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Welcome to Mars, Bob!\n"
     ]
    }
   ],
   "source": [
    "mars = Planet(\"Mars\")\n",
    "bob = Human(\"Bob\")\n",
    "mars.add_human(bob)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a25fda62",
   "metadata": {},
   "source": [
    "Таким образом, мы обновляем население планеты и если мы посмотрим на то что выглядит `mars.population`, мы видим что действительно население планеты обновилось. Kак раз то, что мы делали внутри метода экземпляра `add_human`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5653db75",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "print(len(mars.population))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67cfdcf4",
   "metadata": {},
   "source": [
    "Ничто не мешает нам из методов вызывать другие методы. Давайте посмотрим еще на один пример и убедимся в том, что это действительно возможно, и как это сделать. Мы объявляем класс `Human`, у которого программист предположим решил сделать атрибут `name` и `age` вот такими, то есть назвать их, начиная с символа нижнего подчёркивания. Также у этого класса метод экземпляра `say`, который также начинается с нижнего подчёркивания, а ещё два метода `say_name` и `say_how_old`, которые печатают, сколько человеку лет и какое у него имя."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "8793452f",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Human:\n",
    "    def __init__(self, name, age=0):\n",
    "        self._name = name\n",
    "        self._age = age\n",
    "        \n",
    "    def _say(self, text):\n",
    "        print(text)\n",
    "        \n",
    "    def say_name(self):\n",
    "        self._say(f\"Hello, I am {self._name}\")\n",
    "        \n",
    "    def say_how_old(self):\n",
    "        self._say(f\"I am {self._age} years old\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f407522c",
   "metadata": {},
   "source": [
    "Что значат эти символы нижнего подчёркивания? В других языках программирования вы могли встречаться с `protected`, `рrivate` атрибутами то есть механизмами защиты атрибутов, которые определены внутри класса. В Python-e такого механизма нет и по большому счёту вы можете достучаться до любого атрибута, однако есть такое соглашение, что если атрибут либо метод названы c символа нижнего подчёркивания, то ими пользоваться не рекомендуется потому, что в дальнейших версиях той или иной библиотеки программист, который пишет может либо отказаться от этих атрибутов или методов, начинающихся с символа нижнего подчеркивания, либо поменять их поведение каким-то образом.\n",
    "\n",
    "Однако у нас есть класс, у которого есть два публичных метода, `say_name` и `say_how_old`, оба эти методы вызывают приватный метод `say`. Посмотрим как этим пользоваться."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "94893dfc",
   "metadata": {},
   "outputs": [],
   "source": [
    "bob = Human(\"Bob\", age=29)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de0e06b9",
   "metadata": {},
   "source": [
    "Мы объявляем переменным Боб, который является экземпляром класса `Human` и дальше мы можем вызывать методы, которые внутри себя будут вызывать другой метод, и мы видим что печатается то, что мы хотим."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "57686020",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello, I am Bob\n",
      "I am 29 years old\n"
     ]
    }
   ],
   "source": [
    "bob.say_name()\n",
    "bob.say_how_old()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04f544c3",
   "metadata": {},
   "source": [
    "А вот так вот обращаться к атрибуту экземпляра и методу экземпляра не рекомендуется как раз потому что эти атрибуты и метод начинаются с символа нижнего подчёркивания."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "363cb13b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Bob\n",
      "Whatever we want\n"
     ]
    }
   ],
   "source": [
    "# не рекомендуется!\n",
    "print(bob._name)\n",
    "# не рекомендуется!\n",
    "bob._say(\"Whatever we want\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02b00ebd",
   "metadata": {},
   "source": [
    "Несмотря на то, что Python предоставляет эту возможность. \n",
    "\n",
    "Другой концепт, который есть в реализации классов на Python, это так называемый метод класса либо `classmethod`. Зачем это может быть нужно? Так может получиться, что вам нужно объявить метод, но этот метод не привязан к конкретному экземпляру, но в тоже время он вовлекает класс в свою работу тем или иным образом, сам класс, то есть сам класс, которым вы оперируете, лучше всего посмотреть на примере."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "78cf5748",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Event:\n",
    "    def __init__(self, description, event_date):\n",
    "        self.description = description\n",
    "        self.date = event_date\n",
    "        \n",
    "    def __str__(self):\n",
    "        return f\"Event \\\"{self.description}\\\" at {self.date}\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "901db4da",
   "metadata": {},
   "source": [
    "Давайте отвлечемся немножко от планет и людей и посмотрим класс `Event`, класс, который описывает события. У этого класса есть описание и дата, когда это событие происходит, также я переопределил метод `str`, чтобы когда мы печатали `event` на экран выводилось что-то осмысленное.\n",
    "\n",
    "Посмотрим как им пользоваться. Мы получаем текущую дату используя модуль `datetime` стандартной библиотеки Python, и дальше как обычно создаем экземпляр класса `Event`, инициализируя его с помощью описания и даты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9a620e3b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Event \"Рассказать, что такое @classmethod\" at 2021-06-08\n"
     ]
    }
   ],
   "source": [
    "from datetime import date\n",
    "\n",
    "event_description = \"Рассказать, что такое @classmethod\"\n",
    "event_date = date.today()\n",
    "\n",
    "event = Event(event_description, event_date)\n",
    "print(event)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fdf3c22",
   "metadata": {},
   "source": [
    "На данном слайде мы добавим нашему классу `Event` метод класса. Зачем?\n",
    "\n",
    "Давайте рассмотрим такой пример: повсеместно сейчас распространены мессенджеры, в которых есть масса умных помощников, которые предоставляют тот или иной функционал, а пользователи могут писать им текст и получать в ответ какой-то ответ, пользователь пишет умному помощнику \"Привет! Я хочу добавить такое-то событие на такую то дату\", умный помощник принимает этот ввод пользователя, анализирует его на серверной стороне и добавляет событие в календарь пользователя."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "1d655d29",
   "metadata": {},
   "outputs": [],
   "source": [
    "def extract_description(user_string):\n",
    "    return \"открытие чемпионата мира по футболу\"\n",
    "\n",
    "def extract_date(user_string):\n",
    "    return date(2018, 6, 14)\n",
    "\n",
    "class Event:\n",
    "    def __init__(self, description, event_date):\n",
    "        self.description = description\n",
    "        self.date = event_date\n",
    "    \n",
    "    def __str__(self):\n",
    "        return f\"Event \\\"{self.description}\\\" at {self.date}\"\n",
    "    \n",
    "    @classmethod\n",
    "    def from_string(cls, user_input):\n",
    "        description = extract_description(user_input)\n",
    "        date = extract_date(user_input)\n",
    "        return cls(description, date)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a590e61d",
   "metadata": {},
   "source": [
    "Давайте посмотрим как это можно было бы сделать на Python-е. А у нас есть все тот же класс `Event`, но мы добавили ему метод `from_string`, который обернули декоратором `classmethod`. `Сlassmethod` - это встроенный объект, вам не нужно его ниоткуда импортировать, данный декоратор делает метод методом класса, в отличие от метода экземпляра, метод класса первым аргументом принимает не ссылку на конкретный экземпляр класса, а сам класс непосредственно, то есть в данном случае это будет класс `Event`, а не конкретный экземпляр. Внутри этого метода мы из пользовательского ввода, в данном случае это `user_input`, каким-то образом выделяем дату, которую пользователь хочет создать и описание события, на примере мы сделали это с помощью коротких функций-заглушек, которые из строки выделяют дату и описание, на самом деле - это нетривиальные задачи. Для того чтобы анализировать пользовательский ввод в таком ключе, требуется специальные библиотеки или даже есть для этого сервисы. Это не так просто, но мы для простоты возвращаем просто какое-то описание и какую-то дату. Получив описание и дату мы можем проинициализировать класс и вернуть экземпляр класса события на основе вот той строки, которую нам передал пользователь и получить экземпляр класса `Event` и как-то дальше с ним оперировать, добавить его в календарь.\n",
    "\n",
    "Таким образом `classmethod` может быть полезен как например альтернативный конструктор вашего класса. Давайте посмотрим, как им пользоваться."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "9c063dcf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Event \"открытие чемпионата мира по футболу\" at 2018-06-14\n"
     ]
    }
   ],
   "source": [
    "event = Event.from_string(\n",
    "    \"добавить в мой календарь открытие чемпионата мира по футболу на 14 июня 2018 года\"\n",
    ")\n",
    "\n",
    "print(event)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ebd41ff",
   "metadata": {},
   "source": [
    "Когда после того как мы всё это объявили, у нас есть класс `Event` и мы можем вызвать метод класса `from_string` и передать в него строку. Произойдёт анализ этой строки и в результате нам вернётся экземпляр класса `Event` и мы видим, что у нас всё получилось, на экран вывелось как раз правильное описание события.\n",
    "\n",
    "Возможно вам сейчас не очень очевидно, зачем нужны класс-методы, но это становится более очевидным, когда появляется наследование. Класс-метод принимает на вход класс и этот класс будет всегда тем, который, внутри которого этот класс-метод описан и вы относительно этого класса можете не только его как-то инициализировать и вернуть, но вы также можете обращаться к атрибутам класса, делать всё что угодно, что вы можете сделать с классом. Внутри стандартной библиотеки класс-методы тоже активно используются. И например, вы знаете, что `dict` - это класс. И соответственно у `dict`а, у словаря, есть метод `fromkeys`. Это как раз метод класса, который принимая какой-то итерабельный объект, возвращает нам проинициализированный словарь."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "06466ac7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'1': None, '2': None, '3': None, '4': None, '5': None}"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dict.fromkeys(\"12345\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2091ae0",
   "metadata": {},
   "source": [
    "В данном случае мы передали в него строку и получили словарь, где ключ - это элeменты последовательности в строке.\n",
    "\n",
    "Мы научились объявлять и работать с методами экземпляров, а также посмотрели на методы класса, которые в отличие от методов экземпляров принимают первым аргументом не ссылку на конкретный экземпляр класса, а сам класс непосредственно. \n",
    "\n",
    "Далее мы посмотрим на другие особенности реализации классов в Python-e и посмотрим на статический метод и на `property`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b3ebb12",
   "metadata": {},
   "source": [
    "Следующим концептом, на который мы посмотрим, является статический метод, либо `staticmethod`. Давайте посмотрим на примере."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "d91edf92",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Human:\n",
    "    def __init__(self, name, age=0):\n",
    "        self.name = name\n",
    "        self.age = age\n",
    "        \n",
    "    @staticmethod\n",
    "    def is_age_valid(age):\n",
    "        return 0 < age < 150"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "848dda48",
   "metadata": {},
   "source": [
    "Может так получиться, что вам нужно объявить метод в контексте класса, но этот метод не оперирует ни ссылкой на конкретный экземпляр класса, ни самим классом непосредственно, как мы видели в методе класса. В таком случае вам может помочь статический метод. Зачем вам может понадобиться статический метод? На самом деле, статический метод — это просто вопрос организации кода, то есть вы его используете тогда, когда вам необходимо, чтобы к нему обращались относительно имени класса либо экземпляра. Давайте посмотрим на примере, как объявить статический метод. Мы объявляем `class Human`, у которого есть имя и возраст, и объявили статический метод `is_age_valid`, который проверяет возраст на соответствие каким-то возрастным ограничениям.\n",
    "\n",
    "Чтобы объявить статический метод, мы воспользовались декоратором `staticmethod`, опять же, это встроенный объект в Python'е, не нужно его ниоткуда импортировать. После того как мы обернули функцию декоратором `staticmethod`, мы получаем статический метод. Статический метод принимает только те аргументы, которые ему передают. Обратите внимание, что здесь нет ни `self`, ни `class` аргументов. Обращаться к нему можно вот так, то есть относительно имени класса самого `Human`, либо относительно экземпляра. И в том, и в другом случае происходит одно и то же, и код отработает."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "a433bb69",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# можно обращаться от имени класса\n",
    "Human.is_age_valid(35)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "e8b724cd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# или от экземпляра:\n",
    "human = Human(\"Old Bobby\")\n",
    "human.is_age_valid(234)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f084a559",
   "metadata": {},
   "source": [
    "Опять же, подчеркну, что в данном случае мы могли бы эту функцию объявить просто как обычную функцию, вне контекста класса, вне пространства имен класса. Но мы решили сделать это так, просто из соображений того, что этим будет удобнее пользоваться."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c9f53ed",
   "metadata": {},
   "source": [
    "Далее, еще один мощный концепт, который есть в языке Python и встречается в стандартной библиотеке и в библиотеках повсеместно, является `property`. `Property`, или по-другому вычисляемые свойства. Зачем они нужны? `Property` позволяют изменять поведение и выполнять какую-то вычислительную работу при обращении к атрибуту экземпляра, либо при изменении атрибута, либо при его удалении. Проще всего это понять на примере. Давайте начнем немножко издалека. Предположим, у нас есть класс `Robot`. У этого класса есть переопределенный метод `init`, который инициализирует экземпляр класса одним атрибутом `power` — это мощность робота."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "9c523914",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Robot:\n",
    "    def __init__(self, power):\n",
    "        self.power = power"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39bdcadc",
   "metadata": {},
   "source": [
    "Соответственно, пользоваться всем этим можно вот так, ничего нового для вас здесь нет."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "bc8b04f7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "200\n"
     ]
    }
   ],
   "source": [
    "wall_e = Robot(100)\n",
    "wall_e.power = 200\n",
    "print(wall_e.power)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e298ac2",
   "metadata": {},
   "source": [
    "Мы инициализируем экземпляр и далее можем в любой момент поменять мощность робота и установить ему новое значение."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "430005b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "wall_e.power = -20"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d666cb5",
   "metadata": {},
   "source": [
    "Предположим, что так получилось, что вы заметили, что другие программисты, которые пользуются вашим классом `Robot`, иногда ставят ему отрицательную мощность, а это невалидное значение, и вам хотелось бы, чтобы когда другие программисты ставят отрицательную мощность, эта мощность на самом деле ставилась бы в ноль. Как можно поступить в таком случае? Первое, что приходит на ум, это отрефакторить наш класс и добавить метод экземпляра `set_power`, который, принимая мощность, будет проверять, что мощность меньше нуля и в таком случае ставить ее в ноль, либо ставить в то значение, которое нам передали."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "c1379a30",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Robot:\n",
    "    def __init__(self, power):\n",
    "        self.power = power\n",
    "        \n",
    "    def set_power(self, power):\n",
    "        if power < 0:\n",
    "            self.power = 0\n",
    "        else:\n",
    "            self.power = power"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93e760ed",
   "metadata": {},
   "source": [
    "Таким образом, пользоваться этим можно уже вот так. Мы инициализируем объект `Robot` и дальше вызываем у него метод `set_power`, который в случае отрицательной мощности поставит ее в ноль, и мы видим, что у нас получилось."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "cfbadc0b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n"
     ]
    }
   ],
   "source": [
    "wall_e = Robot(100)\n",
    "wall_e.set_power(-20)\n",
    "print(wall_e.power)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "192fed6c",
   "metadata": {},
   "source": [
    "Однако здесь есть небольшой нюанс. В таком случае программисту, который использует ваш класс, придется менять свой код. То есть это потребует не только рефакторинга от вас, но и рефакторинга от других программистов. Есть ли способ проще? Есть."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "4cac7f06",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Robot:\n",
    "    def __init__(self, power):\n",
    "        self._power = power\n",
    "    \n",
    "    power = property()\n",
    "        \n",
    "    @power.setter\n",
    "    def power(self, value):\n",
    "        if value < 0:\n",
    "            self._power = 0\n",
    "        else:\n",
    "            self._power = value\n",
    "        \n",
    "    @power.getter\n",
    "    def power(self):\n",
    "        return self._power\n",
    "    \n",
    "    @power.deleter\n",
    "    def power(self):\n",
    "        print(\"make robot useless\")\n",
    "        del self._power"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11ac651e",
   "metadata": {},
   "source": [
    "Как раз здесь и появляется `property`. Давайте посмотрим, что это. В данном случае это тот же самый класс `Robot`, у которого `power` теперь является объектом `property`. Опять же, это встроенный объект, ниоткуда не нужно его импортировать. У этого `property` есть на самом деле три метода — это метод `getter`, метод `setter`, и метод `deleter`. Все эти три метода мы можем переопределить, чтобы менять поведение и выполнять какую-то полезную работу при обращении к атрибуту, при присваивании атрибуту какого-то нового значения либо при удалении атрибута. \n",
    "\n",
    "Давайте посмотрим, как это сделано. Мы объявили `class Robot`, и теперь `_power` у него является приватным атрибутом. А `power` без нижнего подчеркивания теперь является объектом `property`. Вот таким образом он инициализируется. Далее мы можем объявить три метода и обернуть их декораторами. `power.setter` — это тот метод, который будет выполняться, когда мы будем менять атрибут экземпляра `power`. `power.getter` — это тот метод, который будет выполняться, когда мы будем читать атрибут `power`. `power.deleter` — это тот метод, соответственно, который будет выполняться при удалении атрибута.\n",
    "\n",
    "Внутри этих методов мы можем выполнять какую-то полезную работу. Например, в методе `setter`, который принимает значение, которое пользователь пытается присвоить атрибуту, мы проверяем, что это значение меньше нуля, и, соответственно, ставим приватный атрибут power в ноль. То же самое, что мы делали в методе `set_power` чуть ранее. \n",
    "\n",
    "Соответственно, в методе `getter` объекта `property` мы возвращаем значение приватного атрибута `power`. Теперь, когда другой программист будет пользоваться вашим классом, он может по прежнему использовать старый подход и ставить атрибут экземпляру. Даже если он поставит отрицательный атрибут, этот атрибут будет выставлен в ноль в итоге, потому что отработает `setter` метод нашего свойства, нашего `property`.\n",
    "\n",
    "Если мы попробуем удалить этот атрибут, то выполнится `deleter` метод, который мы также переопределили, и, соответственно, удалит атрибут `power` из нашего экземпляра, тем самым сделав робота бесполезным."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "cdf26fee",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n"
     ]
    }
   ],
   "source": [
    "wall_e = Robot(100)\n",
    "wall_e.power = -20\n",
    "print(wall_e.power)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "ace80de7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "make robot useless\n"
     ]
    }
   ],
   "source": [
    "del wall_e.power"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1adc41dc",
   "metadata": {},
   "source": [
    "Иногда нужно как-то модифицировать чтение атрибута и выполнять какую-то полезную работу при чтении, и это единственное, что вам требуется. То есть не нужно менять поведение при изменении значения атрибута либо при его удалении. В таком случае есть более короткая запись."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "5b4c26cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Robot:\n",
    "    def __init__(self, power):\n",
    "        self._power = power\n",
    "        \n",
    "    @property\n",
    "    def power(self):\n",
    "        # здесь могут быть любые полезные вычисления\n",
    "        return self._power"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1be7bf60",
   "metadata": {},
   "source": [
    "Мы можем объявить метод, обернуть его декоратором `property` без всяких суффиксов `getter`, `setter` и `deleter`, и это будет вычисляемым свойством класса, к которому можно обращаться вот так, как вы видите на слайде, то есть просто `power`. Удобно."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "09978b6e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "200"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wall_e = Robot(200)\n",
    "wall_e.power"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4425bc7",
   "metadata": {},
   "source": [
    "На этой лекции мы рассмотрели, что такое статический метод. То есть в отличие от обычного метода экземпляра, который на вход принимает ссылку на конкретный экземпляр, и методы класса, который на вход принимает `class`, с которым идет работа, статический метод не принимает ничего, только те аргументы, которые в него передают. Используется он для того, когда нам из соображений удобства удобно обращаться к функции в контексте класса. Также мы посмотрели на такой концепт, как `property`, то есть вычисляемое свойство."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
