ООП Python
December 7, 2023

Основы ООП в Python #1

Введение

Реальный мир для нас представляется в виде множества объектов, обладающих определёнными свойствами, взаимодействующих между собой и вследствие этого изменяющимися. Эта привычная для взгляда человека картина мира была перенесена в программирование. Объектно-ориентированное программирование – это способ организации программы, позволяющий использовать один и тот же код многократно. В отличие от функций и модулей, ООП позволяет не только разделить программу на фрагменты, но и описать предметы реального мира в виде удобных сущностей — объектов, а также организовать связи между этими объектами.

Запоминаем!

Основными понятиями, используемыми в ООП являются: класс, объект, наследование, инкапсуляция и полиморфизм. В языке Python класс равносилен понятию тип данных.

Классы и объекты.

Почти всегда мы в программах оперируем объектами данных. Например создаётся программы по учёту животных, в частности котов и кошек. Значит мы имеем дело с объектами коты и нужно представить все данные о котах в программе, как единое целое.

И так что следует запомнить и представить у себя в голове: переменные внутри класса называются атрибутами или свойствами, а функции — методами.

Классы, атрибуты классов

class Cats:
    """Краткое описание класса (необязательно)"""
    name = 'Gar'
    age = 5
name и age - атрибуты

Класс описывается с помощью ключевого слова class следом идёт имя класса Cats. Получается простая конструкция:

class <Название класса>[(<Класс1>, ..., <Класс№>)]:
    """ Строка документирования """

После названия класса в круглых скобках можно указать один или несколько базовых классов через запятую. Если же класс не наследует базовые классы, то круглые скобки можно не указывать.

Как воспринимать эту конструкцию?

Фактически сам класс создаёт пространство имён, в данном случае с именем Cats. В этом пространстве имён находятся 2 переменные: name и age. И мы можем обращаться к ним используя имя класса.

Общий вид

Мы можем изменить значения наших свойств используя простое переприсвоение. Сперва обращаемся к классу, далее через точку обращаемся к свойствам и присваиваем значение:

Cats.name = 'Rom'

И так у нас получается:

свойство name поменялось

Если мы хотим увидеть все атрибуты нашего класса, то можно воспользоваться переменной __dict__

Cats.__dict__

Нам выдаёт коллекцию, напоминающий словарь, здесь много атрибутов и среди них есть наши 2: Age и name.

mappingproxy(

{'__module__': '__main__',

'__doc__': 'Краткое описание класса (необязательно)',

'name': 'Rom',

'Age': 5,

'__dict__': <attribute '__dict__' of 'Cats' objects>,

'__weakref__': <attribute '__weakref__' of 'Cats' objects>, '__annotations__': {}})

Теперь рассмотрим другой пример:

class B:
    n = 5
    def adder(v):
        return v + B.n
print(B.n) # Вывод:5
print(B.adder(4)) # Вывод: 9

В примере имена n и adder – это атрибуты класса B. Атрибуты-переменные называют полями или свойствами.

В других языках понятия "поле" и "свойство" не совсем одно и то же).

Атрибутом является n. Атрибуты-функции называются методами. Методом в классе B является adder. Количество свойств и методов в классе может быть любым.

Среди атрибутов можно выделить две группы. Атрибуты первой группы принадлежат самому классу, а не конкретному экземпляру. Доступ к таким атрибутам вне тела класса осуществляется через точечную нотацию через объект объявления класса.

<Имя класса>.<Имя атрибута>

Если этот атрибут — метод, то по аналогии можно его вызвать.

<Имя класса>.<Имя метода>([<Параметры>])

Экземпляр класса

Чтобы разобраться что такое экземпляр класса рассмотрим картинку с примером:

Рисунок 1.

Во первых объекты a и b образуют своё пространство имён, пространство имён экземпляров класса. Во вторых не содержит никаких собственных атрибутов/свойств. Ниже мы увидим что атрибуты у экземпляров берутся у своего родительского класса. Т.е. внутри объектов a, b не существует age и name, они ссылаются на соответствующие атрибуты класса Cats, тем самым делая их общими для всех экземпляров.

Для создания экземпляра нужно создать переменную и присвоить класс

Пример:

a = Cats()
b = Cats()

У нас появляются переменные a, b, которые являются экземпляром класса Cats.

Объект а и объект b являются 2 независимых объекта!!!

Через экземпляр a или b нам доступны 2 атрибута Age и name. Мы можем изменять значения класса через экземпляры. Например:

Cats.Age = 2 //и у нас вместо 5 будет 2 во всех экземплярах класса Cats

Во второй группе находятся атрибуты, принадлежащие конкретному экземпляру. В отличии от многих других языков программирования, эти атрибуты создаются уже во время существования объекта.

При этом если не удается найти атрибут с таким именем у экземпляра, то поиск
продолжается в атрибутах, принадлежащих классу. В связи с этим можно считать, что атрибуты самого класса разделяются между всеми его экземплярами

Обобщаем

Чтобы создать экземпляр класса используется синтаксис:
<название экземпляра класса> = <Название класса>([<Параметры>])

И по аналогии получать доступ к уже существующим атрибутам:
<экземпляр класса>.<Имя атрибута>


Когда экземпляр класса существует, можно задавать атрибуты уникальные для этого самого экземпляра:
<экземпляр класса>.<Имя атрибута> = <Значение>

Давайте рассмотрим немного другой пример:

a.Age = 4
Результат

Как мы видим в одном экземпляре изменился атрибут Age, а в другом нет. Почему так произошло?

Дело в том что когда мы выполняем команду выше, то мы обращаемся к пространству имён объекта 'a'.

Представим что каждый блок это пространство имён

И далее оператор присваивания "=" создаёт переменную с именем Age в текущем пространстве имён. Несмотря на то что мы как бы можем брать эту переменную из другого пространства имён, из внешнего пространства имён, из класса Cats. Но вот этот оператор присваивания работает именно так. Если переменная Age отсутствует в этом текущем пространстве имён, то этот атрибут создаётся в текущем пространстве имён. И в итоге экземпляр 'a' уже имеет свой атрибут Age. А объект 'b' по прежнему ссылается на атрибуты класса Cats. Проверим выше сказанное, выполним простую команду __dict__

a.__dict__

У нас появилось локальное свойство с именем Age и значением 4.

Это важно понимать и знать. Поэтому я так подробно про это рассказываю

На этом принципе в Python построены формирования атрибутов класса и локальных атрибутов для экземпляров класса.

Добавление свойств в класс

Рассмотрим сразу на примерах:

Cats.mass = 2.5 

Соответственно в экземплярах a, b появилось свойство mass.

setattr(Cats, 'mass', 3.5)//mass изменился с 2.5 на 3.5

функция setattr() может добавлять новые свойства, а может изменять как в данном случае

Для удаления свойства в классе:

1 способ.

del Cats.mass

2 способ.

delattr(Cats, 'mass')

Но возможно вы столкнётесь с проблемой несуществующего свойства

И тогда на помощь придёт проверка на существование аргумента:

hasattr(Cats, 'mass')// Вернёт False тк мы удалили ранее 'mass'
А если проверить объект класса, то мы получим True

Обрати внимание!

Cats.mass = 2.5 // Специально добавим в класс новый атрибут
hasattr(a, 'mass')// вернёт True

Функция hasattr() нам говорит о том что, через пространство имён мы можем получить доступ к mass Но она не говорит о том, что данный атрибут находится непосредственно в пространстве имён a

Если посмотреть на коллекцию __dict__ то в ней нет ничего.

Теперь добавим новое свойство mass в экземпляр a

a.mass = 3.5

далее удалим это свойство

И как видим свойство mass снова ссылается на значения в классе Cats

Аналогично мы можем создавать локальные и индивидуальные свойства для экземпляров класса. Пример:

class Point:
    size = 20
    square = 40
    
a = Point()
b = Point()
a.x = 2
a.y = 3
b.x = 5
b.y = 9