Сообщить об ошибке.

Создание экземпляра класса в Python

Содержание:


Процесс создания экземпляра класса.

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

  • Создание нового экземпляра целевого класса;
  • Инициализация нового экземпляра с соответствующим начальным состоянием.

Для выполнения первого шага в классах Python есть специальный метод .__new__(), который отвечает за создание и возврат нового пустого объекта. Затем другой специальный метод .__init__(), принимает результирующий объект вместе с аргументами конструктора класса.

Метод .__init__() принимает новый объект в качестве первого аргумента self. Затем он устанавливает любой требуемый атрибут экземпляра в допустимое состояние, используя аргументы, переданные ему конструктором класса.

Короче говоря, процесс создания экземпляра Python начинается с запуска создателя экземпляра .__new__() для создания нового пустого объекта. Процесс продолжается инициализатором экземпляра .__init__(), который принимает аргументы конструктора для инициализации вновь созданного объекта.

Чтобы изучить внутреннюю работу процесса создания экземпляров Python, рассмотрите следующий пример класса Point, который (в демонстрационных целях) реализует пользовательскую версию обоих методов, .__new__() и .__init__():

# point.py
class Point:
    def __new__(cls, *args, **kwargs):
        print("1. Создается новый экземпляр Point.")
        return super().__new__(cls)

    def __init__(self, x, y):
        print("2. Инициализируется новый экземпляр Point.")
        self.x = x
        self.y = y

    def __repr__(self) -> str:
        return f"{type(self).__name__}(x={self.x}, y={self.y})"

Описание того, что делает этот код:

  • Строка def __new__(cls, *args, **kwargs) определяет метод, который принимает класс в качестве первого аргумента. Обратите внимание, что использование cls в качестве имени этого аргумента является строгим соглашением в Python, точно так же, как использование self для имени текущего экземпляра. Метод также принимает *args и **kwargs, что позволяет передавать неопределенное количество аргументов инициализации базовому экземпляру.
  • Строка return super().__new__(cls) создает и возвращает новый экземпляр Point, вызывая метод родительского класса .__new__() с cls в качестве аргумента. Этот экземпляр будет первым аргументом для .__init__(). В этом примере объект является родительским классом, и вызов super() дает доступ к нему.
  • Строка def __init__(self, x, y) определяет метод конструктора, который отвечает за этап инициализации. Этот метод принимает первый аргумент с именем self, который содержит ссылку на текущий экземпляр и два дополнительных аргумента, x и y.
  • Внутри метода конструктора .__init__() инициализируются начальные значения атрибутов экземпляра Point.x и Point.y соответственно. Для этого он использует входные аргументы x и y.

Сохраним код, представленный выше в файл с именем point.py и запустим интерпретатор Python:

>>> from point import Point
>>> point = Point(21, 42)
# 1. Создается новый экземпляр Point.
# 2. Инициализируется новый экземпляр Point.

>>> point
# Point(x=21, y=42)

Операции, доступные экземплярам класса.

Единственные операции, понятные объектам-экземплярам класса, являются ссылки на атрибуты класса. Существует два вида допустимых имен атрибутов класса, это атрибуты данных и методы класса.

Атрибуты данных соответствуют "переменным экземпляра" в языке Smalltalk или "членам данных" в языке C++. Атрибуты данных класса в Python можно не объявлять, как например это делается с локальным переменным, они появляться динамически, когда им впервые присваивается значение. При этом, динамически созданные атрибуты хранятся в специальном словаре объекта-экземпляра x.__dict__. Например, если x это экземпляр MyClass, то следующий фрагмент кода напечатает значение 16.

Создайте файл test.py с определением класса MyClass и запустите его в интерактивном режиме командой: python3 -i test.py.

# файл `test.py`
class MyClass:
    """Простой пример класса"""
    i = 12345

    def f(self):
        return 'hello world'

# запускаем: $ python3 -i test.py 
>>> x = MyClass()
# обратите внимание, что атрибут 
# `counter` в классе не определен
>>> x.counter = 1
# динамически созданные атрибуты экземпляра класса 
# хранятся в специальных словарях этих экземпляров
>>> x.__dict__
# {'counter': 1}

>>> while x.counter < 10:
...     x.counter = x.counter * 2
... 
>>> x.counter
# 16

# удаляем динамически созданный атрибут
>>> del x.counter
# смотрим специальный словарь экземпляра
>>> x.__dict__
# {}

# пытаемся получить значение
x.counter
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AttributeError: 'MyClass' object has no attribute 'counter'

Другой вид ссылки на атрибут объекта-экземпляра - это метод. Метод - это функция, которая принадлежит объекту класса. В языке Python термин "метод", для экземпляров классов, не уникален: другие типы объектов также могут иметь свои методы. Например, объекты списка list() имеют методы list.append, list.insert, list.remove, list.sort и т. д. Дальше будем использовать термин "метод" исключительно для обозначения "методов объектов экземпляра класса", если явно не указано иное.

Допустимые имена методов объекта экземпляра класса зависят от его класса. По определению, все атрибуты класса, являющиеся объектами функций, определяют соответствующие методы его экземпляров. Таким образом, в нашем примере x.f это допустимая ссылка на связанный метод, так как MyClass.f это функция. Тогда как x.i это НЕ метод, а ссылка на атрибут класса MyClass.i. При этом выражение x.f это объект связанного метода, т.е. не то же самое, что MyClass.f. Так как MyClass.f - это объект функции.

Смотрим пример, который это показывает:

>>> MyClass.f
# <function MyClass.f at 0x7f3c1c409400>
>>> x.f
# <bound method MyClass.f of <__main__.MyClass object at 0x7f3c1fd67710>>

>>> MyClass.i
# 12345
>>> x.i
# 12345