Skip to content

Commit

Permalink
Merge pull request #147 from SemyonSinchenko/stalkermustang-python-cl…
Browse files Browse the repository at this point in the history
…asses-lec10

python lec10 about OOP
  • Loading branch information
vvssttkk authored Aug 30, 2021
2 parents 3ce8a70 + ad8b5de commit d82b753
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions qmlcourseRU/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
title: 🟦 Переменные, типы и вывод
- file: book/pythonblock/python_l5.md
title: 🟦 Условия и сравнения
- file: book/pythonblock/python_l10.md
title: 🟦 Объекты и классы

- part: Основы машинного обучения
chapters:
Expand Down
225 changes: 225 additions & 0 deletions qmlcourseRU/book/pythonblock/python_l10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
---
jupytext:
formats: md:myst
text_representation:
extension: .md
format_name: myst
kernelspec:
display_name: Python 3
language: python
name: python3
---

(python_l10)=

# Объекты, классы и методы

## Описание лекции

В этой лекции мы расскажем про:
- основы ООП: что это и зачем?
- инстанс класса
- на что указывает `self`
- методы классов в уже знакомых примерах
- атрибуты классов

## ООП: Объектно-Ориентированное Программирование
Что же такое объектно-ориентированное программирование? Судя по названию, ключевую роль здесь играют объекты, на которые ориентируется дальнейший процесс программирования. Если мы взглянем на реальный мир, то для нас он предстанет в виде множества объектов, обладающих определенными свойствами, взаимодействующих между собой и вследствие этого изменяющимися. Эта привычная для взгляда человека картина мира была перенесена в программирование. Python - это объектно-ориентированный язык программирования, и **все в нем является объектами**.

**Объект** - это набор данных и инструкций в памяти комьютера, в том числе:
1. Тип объекта;
2. Данные, формирующие объект (контент внутри него);
3. Методы.

```{figure} /_static/pythonblock/oop_l10/class_and_obj1.png
:name: class_and_obj1
:width: 250px
Так можно проиллюстрировать соотношение "класс - объекты". Сверху указаны общие прототипы (прямоугольник, звезда, треугольник), снизу - уникальные экземпляры, со своим цветом и деталями.
```

Как определить тип мы уже знаем. И с объектами на самом деле мы знакомы с самого начала - ведь даже целочисленная переменная `5`, имеющая `type(5) = int`, представляет собой объект. Напомним, что говорилось в курсе ранее: **методы - это те же функции, только существующие не сами по себе, а являющиеся частью класса**.

```{code-cell} ipython3
example_list = list(["что-то", "с"])
print(f"Метод append имеет тип {example_list.append}")
# вот так можно вызвать метод, изменяя сам экземпляр класса
example_list.append("чем-то")
# а вот так можно обратиться к атрибуту конкретного объекта
attr_value = example_list.__doc__
print(f"Значение атрибута: {attr_value}")
```

`list` - это класс, и в первой строчке кода выше создается **объект, или экземпляр класса** `example_list`. Он хранит свой набор данных (контент). Метод `append` общий для всех списков, однако меняет только конкретный экземляр. Обратите внимание: этот метод ничего не возвращает, он меняет сам объект и его контент. Не все методы работают схожим образом, и придется запоминать принцип работы самых распространненых методов. К примеру, сложение чисел или строк внутри Python также осуществляется с помощью методов, однако это не меняет саму переменную, как было рассказано в более ранних лекциях:

```{code-cell} ipython3
# создадим два объекта класса int
a = 3
b = int(4)
# неявно вызовем метод сложения
a + b
# проверим, что содержимое объектов не изменилось
print(f"{a = }, {b = }")
```

Конкретно в данном случае логика работы обусловлена свойствами `mutable`/`immutable` объектов, о чем также упоминалось в одной из предшествующих лекций. `int` неизменяем, и потому при вызове метода сложения возвращает новый объект - ведь самого себя изменить не получится. `list` же может меняться, и `append` меняет содержимое `inplace`.

```{figure} /_static/pythonblock/oop_l10/class_and_obj_house.png
:name: class_and_obj_house
:width: 450px
Другой житейский пример - по одному чертежу (классу) можно построить разные (но похожие!) дома, в которых разные семьи, убранство, разные обои.
```


## Определение классов
Давайте попробуем создать свой первый класс:

```{code-cell} ipython3
# определим классы
class List:
def __init__(self, initial_content = None):
# указывать в виде значения по умолчанию mutable-объекты неправильно
if initial_content is None:
initial_content = []
self.content = initial_content
def append(self, new_element):
# изменение производится inplace, с заменой собственного контента
self.content = self.content + [new_element]
class Int:
def __init__(self, value):
self.value = value
def add(self, second_value):
# изменение не производится, создается новый объект и он же возвращается
return Int(self.value + second_value.value)
# воспроизведем примеры кода выше для List
example_list = List(["что-то", "с"])
print(f"{example_list.content = }")
example_list.append("чем-то")
print(f"{example_list.content = }")
print(f"{type(example_list) = }")
# и для Int
a = Int(3)
b = Int(4)
a.add(b)
print(f"После простого сложения: {a.value = }, {b.value = }")
c = a.add(b)
print(f"После записи в переменную результата: {c.value = }")
```

Что важно заметить и отметить в этом примере:
1. Как объявить класс? С помощью ключевого слова `class`, указания названия класса (таким образом создается новый тип данных) и перечисления методов:
```python
class ИмяКласса:
<код_тела_класса>
```
2. Обязательно определить метод `__init__`. Это метод, вызываемый при создании нового объекта класса и отвечающий за определение его контента. В нем через запятую, как и в обычной функции, перечислить необходимые переменные и, по возможности, их значения по умолчанию;
3. Каждый метод должен первым аргументом принимать **экземпляр объекта** (по соглашению он называется `self`). При этом при вызове метода Python автоматически подставит сам объект, делать это руками не нужно. По сути под капотом происходит следующее:

```{code-cell} ipython3
# вызываем метод класса - не объекта! - и передаем первым аргументом объект,
# который при обычных обстоятельствах будет записан в self
result = Int.add(Int(3), Int(4))
print(f"{result.value}")
```
4. **Атрибутом называется переменная, хранящаяся в экземпляре класса**;
5. В рамках методов атрибуты объекта доступны с использованием ключевого слова `self`. К примеру, `self.data`;
6. Вне методов атрибуты доступны у экземпляров через точку: `some_object.attr_name`;
7. Для создания объекта класса нужно указать имя класса (типа) и передать аргументы: `Int(3)` - как раз оно;
8. Методы как могут возвращать что-либо (в том числе новый объект того же класса), так и ничего не возвращать.

В заключение еще одна картинка, иллюстрирующая суть класса, экземпляра, атрибутов и методов:

```{figure} /_static/pythonblock/oop_l10/class_and_obj_pika.png
:name: class_and_obj_pika
:width: 450px
Who's that pokemon?
```

## Пример использования для хранения состояние
В курсе будет встречаться подобный паттерн, поэтому хочется остановиться поподробнее. Допустим, есть некоторая среда, заданная состоянием, и есть объекты, существующие в этой среде, как-то взаимодействующие между собой (и со средой). Есть набор функций, который осуществляет эти взаимодействия. Тогда удобно ввести некоторый объект `system` класса `System`, который хранит эту информацию, и передавать его как параметр в методы расчета чего-либо (никакой конкретной логики тут не заложено, не пытайтесь понять, за что отвечает `alpha` и прочие переменные - пример абстрактный):

```{code-cell} ipython3
# класс системы, с ее параметрами и состоянием
class System:
def __init__(self, param_vector, alpha, gamma, multiplier = 2.0, energy = 0.0):
self.param_vector = param_vector
self.alpha = alpha
self.gamma = gamma
self.multiplier = multiplier
self.energy = energy
# начальное состояние
self.system_state = [
(alpha - gamma) * multiplier * param
for param
in self.param_vector
]
def first_action(self, object):
# взаимодействие с одним объектом: увеличение энергии, изменение состояния системы
self.gamma -= object.energy
self.energy += object.energy
self.system_state = [param - self.alpha * self.energy for param in self.system_state]
def second_action(self, list_of_object):
# взаимодействие с несколькими объектами - увеличение их энергии
for obj in list_of_object:
obj.energy += self.gamma * self.multiplier
class Object:
def __init__(self, energy = 10.0):
# объект хранит только один атрибут
self.energy = energy
# симуляция одного цикла в системе
def one_system_cycle(system: System, objects = None):
if objects is not None:
system.second_action(objects)
# убираем один последний объект
objects = objects[:-1]
else:
objects = [Object(val) for val in range(5)]
for obj in objects:
system.first_action(obj)
return objects
params = [1.0, 3.0, 4.15, 0.0]
alpha = 5
gamma = 0.18
system = System(params, alpha, gamma, energy = 10)
# изначально объектов нет, они появятся после первого цикла
objects = None
for cycle_num in range(1, 4):
print(f"Состояние системы на итерации {cycle_num}: {system.system_state}.", end="\t")
objects = one_system_cycle(system, objects)
print(f"Всего объектов: {len(objects)}", end="\t")
total_object_energy = sum([obj.energy for obj in objects])
print(f"Суммарная энергия объектов: {total_object_energy}")
```
Обратите внимание на то, что функция `one_system_cycle` не возвращает `system` - ведь мы прямо во время итераций меняем ее состояние, и в последующие моменты времени эти изменения сохраняются, то есть состояние остается. Таким образом, эту систему (среду) - а именно объект, экземпляр класса `System` - как параметр **можно передавать в десятки функций, и каждая из них будет видеть актуальное состояние**.



## Что мы узнали из лекции
- Объекты одного класса отличаются между собой атрибутами, которые придают уникальности;
- Методы классов позволяют описывать логику взаимодействия, **обновлять состояние**;
- Для объявления класса нужно определить **как минимум один метод** `__init__`;
- Классы без методов можно использовать как **удобное хранилище разнородной информации** о схожих объектах (описать разных студентов и их оценки).

Это далеко не все, что можно написать по теме классов и объектов, однако это тот необходимый минимум, что потребуется для прохождения курса. Больше примеров и деталей можно найти, например, по ссылкам: [пример с несколькими взаимодействующими объектами](https://younglinux.info/oopython/ooprogramm), [что такое наследование и зачем оно нужно](https://younglinux.info/oopython/inheritance).

0 comments on commit d82b753

Please sign in to comment.