Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Инстанцирование классов и экземпляры Python: Введение в ООП

Экземпляры

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

Инстанцированием (instantiation) называют процесс (акт) создания на основе класса экземпляра (instance) — такого объекта, который получает доступ ко всему содержимому класса, но при этом обладает и способностью хранить собственные данные. При этом, имея объект, всегда можно узнать, экземпляром какого класса он является.

Давайте объявим класс и создадим пару экземпляров, а заодно и познакомимся с синтаксисом инстанцирования классов:

class Person:
    pass

bob = Person()
bob  # <__main__.Person object at 0x7f133df55fd0>
alice = Person()
alice  # <__main__.Person object at 0x7f133df62048>
bob is alice  # False
bob is Person  # False
alice is Person  # False

Что мы можем увидеть в этом примере? Первое, что бросается в глаза, это вызов класса как функции: Person(). Сходство это — не только внешнее. В Python инстанцирование фактически и является вызовом некоторой функции, которая возвращает новый экземпляр класса.

При выводе объекта класса в REPL можно увидеть строку, похожую на вывод информации о классе, только вместо "class" в строчке упоминается "object".

Также стоит обратить внимание на то, что все экземпляры являются отдельными объектами, поэтому оператор is дает False как при соотнесении экземпляров между собой, так и при соотнесении любого экземпляра с объектом класса (bob, alice и Person — три самостоятельных объекта).

Атрибуты класса и экземпляры

В предыдущем примере класс был пустой. Теперь воспроизведем его, но добавим на этот раз атрибут:

class Person:
    name = 'Noname'

bob, alice = Person(), Person()
bob is alice  # False
bob.name is alice.name  # True
bob.name is Person.name  # True
bob.name  # 'Noname'

Этот пример показывает, что а) и bob, и alice имеют атрибут name, б) значение атрибутов name — общее для всех трех объектов.

Давайте же переименуем Боба:

bob.name = 'Bob'
bob.name is Person.name  # False
Person.name  # 'Noname'
alice.name  # 'Noname'
bob.name  # 'Bob'

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

Атрибут __dict__

Стоит прямо сейчас заглянуть "под капот" объектной системы Python, чтобы вы в дальнейшем могли исследовать объекты самостоятельно. Это и интересно, и полезно — как при обучении, так и при отладке объектного кода.

Итак, внутри каждого объекта Python хранит… словарь! Имена атрибутов в пространствах имен выступают ключами этого словаря, а значения являются ссылками на другие объекты. Словарь этот всегда называется __dict__ и тоже является атрибутом. Обращаясь к этому словарю, вы можете получить доступ к значениям атрибутов:

Person.__dict__['name']  # 'Noname'
bob.__dict__['name']  # 'Bob'
alice.__dict__['name']
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# KeyError: 'name'

Присмотритесь, и вы увидите: у bob в __dict__ есть его собственное имя, а у alice собственного имени нет. Но при обращении к атрибуту привычным способом "через точку", вы видите имя и у alice! Как же это работает?

Дело в том, что машинерия объектной системы Python при обращении к атрибуту сначала ищет атрибут в словаре экземпляра. Но если там соответствующего ключа не нашлось, то атрибут ищется уже в классе. Именно так alice получает имя: Python находит его в классе Person.

Надо сказать, что это очень разумный подход! Да, Python мог бы копировать словарь класса при инстанцировании. Но это привело бы к излишнему потреблению памяти. А вот "коллективное использование", напротив, позволяет память экономить!

И, конечно же, словарь __dict__ объекта может быть изменен. Когда мы давали Бобу имя, мы на самом деле сделали что-то такое:

bob.__dict__['name'] = 'Bob'

Мы даже можем добавить Бобу фамилию и сделать это через модификацию __dict__:

bob.__dict__['surname'] = 'Smith'
bob.surname  # 'Smith'
'surname' in Person.__dict__  # False

А ведь у класса не было атрибута surname! Каждый экземпляр класса тоже является самостоятельным пространством имен, пригодным для расширения в процессе исполнения программы (за счет использования под капотом словарей, как вы теперь знаете!).

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

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

bob.__class__  # <class '__main__.Person'>
bob.__class__ is Person  # True

Как вы уже могли заметить, в Python многие "внутренние штуки" имеют имена, заключенные в двойные символы подчеркивания. В разговоре питонисты обычно проговаривают подобные имена примерно так: "дАндер-класс", что является калькой с "dunder class", где "dunder", в свою очередь, это сокращение от "double underscore", то есть "двойной символ подчеркивания". Полезно запомнить этот стиль именования!

А еще стоит запомнить, что практически всегда, когда вы хотите использовать что-то, названное в dunder-стиле, "есть способ лучше"! Так с __dict__ напрямую работать не приходится, потому что есть возможность обращаться к атрибутам "через точку". Вот и __class__ в коде встречается редко. А рекомендуемый способ проверки принадлежности к классу выглядит так:

isinstance(bob, Person)  # True

Запомните эту функцию! Она "умнее", чем вам может сейчас показаться. В дальнейшем мы увидим более интересные примеры ее применения.


Дополнительные материалы

  1. Инстанцирование

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 7 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»