Объектно-ориентированное программирование

Класс, __init__, атрибуты экземпляра и методы

Содержание курса

В предыдущем уроке мы разобрали *args и **kwargs — гибкие сигнатуры функций, которые принимают произвольное количество аргументов. Этот же механизм скоро пригодится в конструкторах классов, но сначала нужно понять, что такое класс и как он устроен изнутри.

Определение класса и роль init

Класс в Python объявляется ключевым словом class, после которого идёт имя и двоеточие. По соглашению имена классов пишут в CamelCase — Counter, UserProfile, OrderItem. Тело класса — это обычный блок с отступом, внутри которого определяются методы.

class Counter:
    pass

Пока это просто пустой класс. Он уже работает: Python создал новый тип Counter. Но чтобы объект при создании получал начальное состояние, нужен специальный метод __init__.

__init__ — это инициализатор экземпляра. Python вызывает его автоматически сразу после того, как создан новый объект. Важно: __init__ не выделяет память и не «конструирует» объект в низкоуровневом смысле — этим занимается __new__. Задача __init__ проще: получить только что созданный пустой объект и заполнить его данными.

Первый параметр __init__ — это self. Он ссылается на тот самый объект, который был только что создан. Именно через self метод знает, с каким объектом работает.

class Counter:
    def __init__(self, name):
        print(f"Создаётся объект Counter с именем {name}")
        print(f"self — это объект по адресу: {id(self)}")

Когда Python выполняет Counter('A'), он:

  1. Создаёт новый объект типа Counter.
  2. Передаёт этот объект в __init__ как self.
  3. Передаёт 'A' как name.
  4. Выполняет тело __init__.

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

Сам вызов Counter('A') выглядит как вызов функции, но это вызов класса как фабрики объектов. Python знает, что нужно создать экземпляр Counter и запустить __init__ с переданными аргументами. Аргумент для self не указывают явно — интерпретатор передаёт его сам.

class Counter:
    def __init__(self, name):
        print(f"__init__ вызван для объекта {id(self)}, name={name}")

c = Counter('A')
print(type(c))   # <class '__main__.Counter'>

После выполнения этого кода c — полноценный объект типа Counter. __init__ уже отработал. На следующем шаге мы запишем в self первые данные и превратим инициализатор в нечто полезное.