Введение
В этом практическом занятии (лабораторной работе) вы узнаете, как объекты Python представлены внутри системы, и поймете механизмы назначения и поиска атрибутов. Эти концепции являются фундаментальными для понимания того, как Python управляет данными и поведением внутри объектов.
Кроме того, вы исследуете взаимосвязь между классами и экземплярами и рассмотрите роль определений классов в объектно-ориентированном программировании. Эти знания расширят ваше понимание объектно-ориентированных возможностей Python.
Создание простого класса для акций
На этом этапе мы создадим простой класс для представления акций. Понимание того, как создавать классы, является фундаментальным в Python, так как это позволяет нам моделировать реальные объекты и их поведение. Этот простой класс для акций станет нашей отправной точкой для исследования того, как объекты Python работают внутри системы.
Для начала нам нужно открыть интерактивную оболочку Python. Интерактивная оболочка Python - это отличное место для экспериментов с кодом на Python. Вы можете поочередно вводить и выполнять команды на Python. Чтобы открыть ее, введите следующую команду в терминале:
python3
После ввода команды вы увидите приглашение Python (>>>). Это означает, что вы теперь находитесь внутри интерактивной оболочки Python и можете начинать писать код на Python.
Теперь определим класс SimpleStock. Класс в Python похож на чертеж для создания объектов. Он определяет атрибуты (данные) и методы (функции), которые будут у объектов этого класса. Вот как определить класс SimpleStock с необходимыми атрибутами и методами:
>>> class SimpleStock:
... def __init__(self, name, shares, price):
... self.name = name
... self.shares = shares
... self.price = price
... def cost(self):
... return self.shares * self.price
...
В приведенном выше коде метод __init__ является специальным методом в классах Python. Он называется конструктором и используется для инициализации атрибутов объекта при его создании. Параметр self ссылается на экземпляр класса, который создается. Метод cost вычисляет общую стоимость акций, умножая количество акций на цену за одну акцию.
После определения класса мы можем создать экземпляры класса SimpleStock. Экземпляр - это фактический объект, созданный по чертежу класса. Создадим два экземпляра, чтобы представить разные акции:
>>> goog = SimpleStock('GOOG', 100, 490.10)
>>> ibm = SimpleStock('IBM', 50, 91.23)
Эти экземпляры представляют 100 акций Google по цене $490.10 за одну акцию и 50 акций IBM по цене $91.23 за одну акцию. Каждый экземпляр имеет свою собственную набор значений атрибутов.
Проверим, что наши экземпляры работают правильно. Мы можем сделать это, проверив их атрибуты и вычислив их стоимость. Это поможет нам убедиться, что класс и его методы работают как ожидается.
>>> goog.name
'GOOG'
>>> goog.shares
100
>>> goog.price
490.1
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5
Метод cost() умножает количество акций на цену за одну акцию, чтобы вычислить общую стоимость владения этими акциями. Запустив эти команды, мы можем видеть, что экземпляры имеют правильные значения атрибутов и что метод cost вычисляет стоимость правильно.
Исследование внутреннего словаря объектов
В Python объекты являются фундаментальным понятием. Объект можно представить как контейнер, который хранит данные и имеет определенное поведение. Одним из интересных аспектов объектов Python является то, как они хранят свои атрибуты. Атрибуты - это, по сути, переменные, принадлежащие объекту. Python хранит эти атрибуты в специальном словаре, который можно получить доступ через атрибут __dict__. Этот словарь является внутренней частью объекта, и именно здесь Python отслеживает все данные, связанные с этим объектом.
Давайте более подробно рассмотрим эту внутреннюю структуру, используя наши экземпляры класса SimpleStock. В Python экземпляр - это отдельный объект, созданный на основе класса. Например, если SimpleStock - это класс, то goog и ibm - это экземпляры этого класса.
Чтобы увидеть внутренний словарь этих экземпляров, мы можем использовать интерактивную оболочку Python. Интерактивная оболочка Python - это отличный инструмент для быстрого тестирования кода и просмотра результатов. В интерактивной оболочке Python введите следующие команды, чтобы проверить атрибут __dict__ наших экземпляров:
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1}
>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}
При выполнении этих команд вывод показывает, что каждый экземпляр имеет свой собственный внутренний словарь. Этот словарь содержит все атрибуты экземпляра. Например, в экземпляре goog атрибуты name, shares и price хранятся в словаре с соответствующими значениями. Именно так Python реализует атрибуты объектов "за кулисами". Каждый объект имеет приватный словарь, который хранит все его атрибуты.
Важно понять, что атрибут __dict__ показывает о внутренней реализации наших объектов:
- Ключи в словаре соответствуют именам атрибутов. Например, в экземпляре
googключ'name'соответствует атрибутуnameобъекта. - Значения в словаре - это значения атрибутов. Таким образом, значение
'GOOG'является значением атрибутаnameдля экземпляраgoog. - Каждый экземпляр имеет свой собственный отдельный
__dict__. Это означает, что атрибуты одного экземпляра независимы от атрибутов другого экземпляра. Например, атрибутsharesэкземпляраgoogможет отличаться от атрибутаsharesэкземпляраibm.
Подход на основе словарей позволяет Python быть очень гибким в работе с объектами. Как мы увидим на следующем этапе, мы можем использовать эту гибкость для модификации и доступа к атрибутам объектов различными способами.
Добавление и изменение атрибутов объектов
В Python объекты реализованы на основе словарей. Эта реализация придает Python высокую степень гибкости при работе с атрибутами объектов. В отличие от некоторых других языков программирования, Python не ограничивает атрибуты объекта только теми, которые определены в его классе. Это означает, что вы можете добавлять новые атрибуты объекту в любое время, даже после его создания.
Давайте исследуем эту гибкость, добавив новый атрибут одному из наших экземпляров. Предположим, у нас есть экземпляр с именем goog некоторого класса. Мы добавим к нему атрибут date:
>>> goog.date = "6/11/2007"
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007'}
Здесь мы добавили новый атрибут date к экземпляру goog. Обратите внимание, что этот атрибут date не был определен в классе SimpleStock. Этот новый атрибут существует только в экземпляре goog. Чтобы убедиться в этом, проверим экземпляр ibm:
>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}
>>> hasattr(ibm, 'date')
False
Как мы видим, экземпляр ibm не имеет атрибута date. Это показывает три важных характеристики объектов Python:
- Каждый экземпляр имеет свой уникальный набор атрибутов.
- Атрибуты могут быть добавлены к объекту после его создания.
- Добавление атрибута к одному экземпляру не влияет на другие экземпляры.
Теперь давайте попробуем другой способ добавления атрибута. Вместо использования точечной нотации мы напрямую манипулируем внутренним словарем объекта. В Python каждый объект имеет специальный атрибут __dict__, который хранит все его атрибуты в виде пар ключ - значение.
>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007', 'time': '9:45am'}
Прямым изменением словаря __dict__ мы добавили новый атрибут time к экземпляру goog. Когда мы обращаемся к goog.time, Python ищет ключ 'time' в словаре __dict__ и возвращает соответствующее ему значение.
Эти примеры показывают, что объекты Python по сути являются словарями с дополнительными возможностями. Гибкость объектов Python позволяет осуществлять динамические изменения, что очень мощно и удобно в программировании.
Понимание отношения между классами и экземплярами
Теперь мы рассмотрим, как классы и экземпляры связаны в Python, и как работает поиск методов. Это важное понятие, так как оно помогает понять, как Python находит и использует методы и атрибуты при работе с объектами.
Сначала проверим, какому классу принадлежат наши экземпляры. Знание класса экземпляра является важным, так как оно показывает, где Python будет искать методы и атрибуты, связанные с этим экземпляром.
>>> goog.__class__
<class '__main__.SimpleStock'>
>>> ibm.__class__
<class '__main__.SimpleStock'>
Оба экземпляра имеют ссылку на класс SimpleStock. Эта ссылка похожа на указатель, который Python использует, когда ему нужно найти метод. Когда вы вызываете метод у экземпляра, Python использует эту ссылку для нахождения соответствующего определения метода.
Когда вы вызываете метод у экземпляра, Python выполняет следующие шаги:
- Он ищет атрибут в
__dict__экземпляра.__dict__экземпляра - это своего рода хранилище, где хранятся все атрибуты, специфичные для этого экземпляра. - Если атрибут не найден, он проверяет
__dict__класса.__dict__класса хранит все атрибуты и методы, общие для всех экземпляров этого класса. - Если атрибут найден в классе, он возвращает этот атрибут.
Давайте посмотрим, как это работает на практике. Сначала убедимся, что метод cost отсутствует в словарях экземпляров. Этот шаг помогает понять, что метод cost не специфичен для каждого экземпляра, а определен на уровне класса.
>>> 'cost' in goog.__dict__
False
>>> 'cost' in ibm.__dict__
False
Тогда откуда берется метод cost? Давайте проверим класс. Посмотрев на __dict__ класса, мы можем узнать, где определен метод cost.
>>> SimpleStock.__dict__['cost']
<function SimpleStock.cost at 0x7f...>
Метод определен в классе, а не в экземплярах. Когда вы вызываете goog.cost(), Python не находит cost в goog.__dict__, поэтому он ищет в SimpleStock.__dict__ и находит его там.
Вы можете даже вызвать метод напрямую из словаря класса, передав экземпляр в качестве первого аргумента (который становится self). Это показывает, как Python внутренне вызывает методы, когда вы используете обычный синтаксис instance.method().
>>> SimpleStock.__dict__['cost'](goog)
49010.0
>>> SimpleStock.__dict__['cost'](ibm)
4561.5
В сущности, это то, что Python делает "за кулисами", когда вы вызываете goog.cost().
Теперь давайте добавим атрибут класса. Атрибуты класса общие для всех экземпляров. Это означает, что все экземпляры класса могут получить доступ к этому атрибуту, и он хранится только один раз на уровне класса.
>>> SimpleStock.exchange = 'NASDAQ'
>>> goog.exchange
'NASDAQ'
>>> ibm.exchange
'NASDAQ'
Оба экземпляра могут получить доступ к атрибуту exchange, но он не хранится в их индивидуальных словарях. Давайте убедимся в этом, проверив словари экземпляра и класса.
>>> 'exchange' in goog.__dict__
False
>>> 'exchange' in SimpleStock.__dict__
True
>>> SimpleStock.__dict__['exchange']
'NASDAQ'
Это демонстрирует, что:
- Атрибуты класса общие для всех экземпляров. Все экземпляры могут получить доступ к одному и тому же атрибуту класса без создания собственной копии.
- Python сначала проверяет словарь экземпляра, а затем словарь класса. Это порядок, в котором Python ищет атрибуты, когда вы пытаетесь получить доступ к ним у экземпляра.
- Классы служат хранилищем для общих данных и поведения (методов). Класс хранит все общие атрибуты и методы, которые могут быть использованы всеми его экземплярами.
Если мы изменим атрибут экземпляра с тем же именем, он перекроет атрибут класса. Это означает, что когда вы обращаетесь к атрибуту у этого экземпляра, Python будет использовать значение, специфичное для экземпляра, вместо значения на уровне класса.
>>> ibm.exchange = 'NYSE'
>>> ibm.exchange
'NYSE'
>>> goog.exchange ## По-прежнему используется атрибут класса
'NASDAQ'
>>> ibm.__dict__['exchange']
'NYSE'
Теперь у ibm есть собственный атрибут exchange, который перекрывает атрибут класса, в то время как goog по-прежнему использует атрибут класса.
Резюме
В этом практическом занятии (лабораторной работе) вы узнали о внутреннем устройстве объектной системы Python и нескольких ключевых концепциях. Во - первых, объекты Python хранят атрибуты в словаре, к которому можно получить доступ через атрибут __dict__, что обеспечивает гибкость. Во - вторых, вы поняли, как работают присваивание и поиск атрибутов, включая динамическое добавление атрибутов и порядок проверки атрибутов.
Кроме того, вы исследовали взаимосвязь между классами и экземплярами, где классы содержат общие данные и поведение, а экземпляры сохраняют свое собственное состояние. Вы также обнаружили, как работают вызовы методов, когда методы в классе воздействуют на экземпляры с помощью параметра self. Понимание этих концепций углубляет ваше понимание объектно - ориентированной модели Python и полезно для отладки, проектирования иерархий классов и изучения продвинутых возможностей.