Python: Введение в ООП
Теория: Инстанцирование классов и экземпляры
Экземпляры
Класс, как мы уже увидели, может хранить данные. Но типичный класс присутствует в программе в единственном экземпляре. Поэтому сам по себе класс не очень полезен, ведь хранить определения можно и в модулях. Весь смысл использования классов заключается в их инстанцировании.
Инстанцированием (instantiation) называют создание на основе класса экземпляра (instance) — такого объекта, который получает доступ ко всему содержимому класса, но при этом обладает и способностью хранить собственные данные. При этом, имея объект, всегда можно узнать, экземпляром какого класса он является.
Давайте объявим класс и создадим пару экземпляров, а заодно и познакомимся с синтаксисом инстанцирования классов:
Что мы можем увидеть в этом примере? Первое, что бросается в глаза, это вызов класса как функции: Person(). Сходство это — не только внешнее. В Python инстанцирование фактически и является вызовом некоторой функции, которая возвращает новый экземпляр класса.
При выводе объекта класса в REPL можно увидеть строку, похожую на вывод информации о классе, только вместо "class" в строчке упоминается "object".
Также стоит обратить внимание на то, что все экземпляры являются отдельными объектами, поэтому оператор is дает False как при соотнесении экземпляров между собой, так и при соотнесении любого экземпляра с объектом класса (bob, alice и Person — три самостоятельных объекта).
Атрибуты класса и экземпляры
В предыдущем примере класс был пустой. Теперь воспроизведем его, но добавим на этот раз атрибут:
Этот пример показывает, что bob, и alice имеют атрибут name, и что значение атрибутов name — общее для всех трех объектов.
Давайте же переименуем Боба:
Вот вы и увидели то самое "собственное состояние объекта". Person продолжает давать имя всем экземплярам, пока те не изменят значение своего атрибута. В момент присваивания нового значения атрибуту экземпляра, экземпляр получает свой собственный атрибут.
Атрибут __dict__
Стоит прямо сейчас заглянуть "под капот" объектной системы Python, чтобы вы в дальнейшем могли исследовать объекты самостоятельно. Это и интересно, и полезно — как при обучении, так и при отладке объектного кода.
Итак, внутри каждого объекта Python хранит… словарь. Имена атрибутов в пространствах имен выступают ключами этого словаря, а значения являются ссылками на другие объекты. Словарь этот всегда называется __dict__ и тоже является атрибутом. Обращаясь к этому словарю, вы можете получить доступ к значениям атрибутов:
Присмотритесь, и вы увидите: у bob в __dict__ есть его собственное имя, а у alice собственного имени нет. Но при обращении к атрибуту привычным способом "через точку", вы видите имя и у alice. Как же это работает?
Дело в том, что Python при обращении к атрибуту сначала ищет атрибут в словаре экземпляра. Но если там соответствующего ключа не нашлось, то атрибут ищется уже в классе. Именно так alice получает имя – Python находит его в классе Person.
Надо сказать, что это очень разумный подход. Да, Python мог бы копировать словарь класса при инстанцировании. Но это привело бы к излишнему потреблению памяти. А вот "коллективное использование", напротив, позволяет память экономить.
И, конечно же, словарь __dict__ объекта может быть изменен. Когда мы давали Бобу имя, мы на самом деле сделали что-то такое:
Мы даже можем добавить Бобу фамилию и сделать это через модификацию __dict__:
А ведь у класса не было атрибута surname. Каждый экземпляр класса тоже является самостоятельным пространством имен, пригодным для расширения в процессе исполнения программы. За счет использования под капотом словарей, как вы теперь знаете.
Проверка принадлежности экземпляра к классу
Выше мы уже упоминали, что объект всегда связан с классом. Эта связь заключается в наличии у экземпляра атрибута __class__, который является ссылкой на объект класса:
Как вы уже могли заметить, в Python многие "внутренние штуки" имеют имена, заключенные в двойные символы подчеркивания. В разговоре питонисты обычно проговаривают подобные имена примерно так: "дАндер-класс", что является калькой с "dunder class", где "dunder", в свою очередь, это сокращение от "double underscore", то есть "двойной символ подчеркивания". Полезно запомнить этот стиль именования.
А еще стоит запомнить, что практически всегда, когда вы хотите использовать что-то, названное в dunder-стиле, "есть способ лучше". Так с __dict__ напрямую работать не приходится, потому что есть возможность обращаться к атрибутам "через точку". Вот и __class__ в коде встречается редко. А рекомендуемый способ проверки принадлежности к классу выглядит так:
Запомните эту функцию. Она "умнее", чем вам может сейчас показаться. В дальнейшем мы увидим более интересные примеры ее применения.

.png)


