Основы ООП в Python #1
Введение
Реальный мир для нас представляется в виде множества объектов, обладающих определёнными свойствами, взаимодействующих между собой и вследствие этого изменяющимися. Эта привычная для взгляда человека картина мира была перенесена в программирование. Объектно-ориентированное программирование – это способ организации программы, позволяющий использовать один и тот же код многократно. В отличие от функций и модулей, ООП позволяет не только разделить программу на фрагменты, но и описать предметы реального мира в виде удобных сущностей — объектов, а также организовать связи между этими объектами.
Основными понятиями, используемыми в ООП являются: класс, объект, наследование, инкапсуляция и полиморфизм. В языке Python класс равносилен понятию тип данных.
Классы и объекты.
Почти всегда мы в программах оперируем объектами данных. Например создаётся программы по учёту животных, в частности котов и кошек. Значит мы имеем дело с объектами коты и нужно представить все данные о котах в программе, как единое целое.
И так что следует запомнить и представить у себя в голове: переменные внутри класса называются атрибутами или свойствами, а функции — методами.
Классы, атрибуты классов
class Cats:
"""Краткое описание класса (необязательно)"""
name = 'Gar'
age = 5name и age - атрибуты
Класс описывается с помощью ключевого слова class следом идёт имя класса Cats. Получается простая конструкция:
class <Название класса>[(<Класс1>, ..., <Класс№>)]:
""" Строка документирования """После названия класса в круглых скобках можно указать один или несколько базовых классов через запятую. Если же класс не наследует базовые классы, то круглые скобки можно не указывать.
Как воспринимать эту конструкцию?
Фактически сам класс создаёт пространство имён, в данном случае с именем Cats. В этом пространстве имён находятся 2 переменные: name и age. И мы можем обращаться к ним используя имя класса.
Мы можем изменить значения наших свойств используя простое переприсвоение. Сперва обращаемся к классу, далее через точку обращаемся к свойствам и присваиваем значение:
Cats.name = 'Rom'
Если мы хотим увидеть все атрибуты нашего класса, то можно воспользоваться переменной __dict__
Cats.__dict__
Нам выдаёт коллекцию, напоминающий словарь, здесь много атрибутов и среди них есть наши 2: Age и name.
'__doc__': 'Краткое описание класса (необязательно)',
'__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. Количество свойств и методов в классе может быть любым.
Среди атрибутов можно выделить две группы. Атрибуты первой группы принадлежат самому классу, а не конкретному экземпляру. Доступ к таким атрибутам вне тела класса осуществляется через точечную нотацию через объект объявления класса.
Если этот атрибут — метод, то по аналогии можно его вызвать.
<Имя класса>.<Имя метода>([<Параметры>])
Экземпляр класса
Чтобы разобраться что такое экземпляр класса рассмотрим картинку с примером:
Во первых объекты a и b образуют своё пространство имён, пространство имён экземпляров класса. Во вторых не содержит никаких собственных атрибутов/свойств. Ниже мы увидим что атрибуты у экземпляров берутся у своего родительского класса. Т.е. внутри объектов a, b не существует age и name, они ссылаются на соответствующие атрибуты класса Cats, тем самым делая их общими для всех экземпляров.
Для создания экземпляра нужно создать переменную и присвоить класс
a = Cats() b = Cats()
У нас появляются переменные a, b, которые являются экземпляром класса Cats.
Через экземпляр 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() может добавлять новые свойства, а может изменять как в данном случае
Для удаления свойства в классе:
del Cats.mass
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