$ sudo teach IT
МОДУЛЬ 2 · УРОК 2

Конструктор __init__() и атрибуты

В прошлом уроке мы создавали объекты — но они были пустыми, без каких-либо данных внутри. Сегодня мы научимся «наполнять» объекты при рождении: задавать имя, возраст, email — всё что нужно. Познакомимся с конструктором и атрибутами.

⏱ ~30 минут 🎓 Для новичков 🐍 Python 3

🤔 Почему пустых объектов недостаточно?

В прошлом уроке мы написали класс Dog с pass внутри и создали несколько объектов. Технически — работает. Но смысл нулевой.

Представьте: вы делаете систему пользователей для сайта. Создаёте объект user = User() — и что? У него нет имени, нет email, нет возраста. Это просто пустая коробка. Как отличить одного пользователя от другого? Никак.

Нам нужен механизм, который позволяет передавать данные в момент создания объекта. Именно для этого и существует конструктор.

💡 Аналогия: представьте анкету при регистрации. Когда новый пользователь заходит на сайт — вы просите его заполнить имя, email, возраст. В ООП конструктор — это и есть та самая анкета. Он срабатывает в момент «рождения» объекта и заполняет его начальными данными.

🔨 Что такое конструктор?

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

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

Почему __init__, а не просто init? Двойные подчёркивания с обеих сторон — это в Python сигнал: «этот метод особенный, он встроен в язык». Такие методы есть у всех объектов Python — у строк, чисел, списков. Детально изучим эту тему позже; пока просто запомните написание: два подчёркивания слева, два справа.

🎯 Ключевая идея: вы пишете user = User("Алекс", 25) — Python создаёт объект и немедленно вызывает __init__, передавая туда "Алекс" и 25. Всё происходит за одну строку кода.

⚖️ До и после: класс без __init__ и с ним

Лучший способ понять пользу конструктора — сравнить «до» и «после». Посмотрим на оба варианта:

# --- БЕЗ __init__ ---
class UserV1:
    pass

user1 = UserV1()
# Объект создан, но он совершенно пустой.
# Нет имени, нет email, нет ничего.
print(user1)  # <__main__.UserV1 object at 0x...>


# --- С __init__ ---
class UserV2:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

user2 = UserV2("Алекс", 25, "alex@example.com")
# Объект сразу наполнен данными!
print(user2.name)   # Алекс
print(user2.age)    # 25
print(user2.email)  # alex@example.com

Разница колоссальная. Во втором случае объект «живой» — у него есть данные, с которыми можно работать. Именно ради этого мы и пишем конструктор.

Что происходит, если не определить __init__? Python не выдаёт ошибку — он автоматически использует базовый конструктор, который просто создаёт пустой объект. Это именно то, что мы делали в прошлом уроке с pass. Так что класс без __init__ — рабочий код, просто без начальных данных.

👆 Первый параметр self

Посмотрите внимательно на сигнатуру метода:

def __init__(self, name, age, email):

Первый параметр — self. Что это такое? Проще говоря, self — это ссылка на сам объект, который сейчас создаётся. Когда Python вызывает __init__, он первым делом передаёт туда только что созданный объект, чтобы метод знал, «кого» инициализировать.

Когда вы пишете UserV2("Алекс", 25, "alex@example.com") — вы передаёте три аргумента. Но в __init__ их четыре параметра! Всё потому, что Python автоматически подставляет сам объект как первый аргумент — вам не нужно передавать self вручную.

📌 Важно: название self — это конвенция, а не жёсткое требование Python. Технически можно написать def __init__(this, name) — и всё будет работать. Но так никто не делает. Во всём мире Python-разработчики используют слово self, и вы тоже должны. Об этом параметре мы поговорим подробнее в следующем уроке — там целая отдельная тема.

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

Атрибут экземпляра — это переменная, которая «принадлежит» конкретному объекту. Не всем объектам класса сразу, а именно этому, конкретному.

Давайте разберём это подробнее. Когда вы создаёте двух пользователей:

user_a = UserV2("Алекс", 25, "alex@example.com")
user_b = UserV2("Мария", 30, "maria@example.com")

У объекта user_a есть свой атрибут name со значением "Алекс". У объекта user_b есть свой атрибут name со значением "Мария". Это разные переменные в разных областях памяти, хотя и называются одинаково. Изменение имени user_a никак не затронет user_b.

👤

user_a

name = "Алекс"
age = 25
email = "alex@example.com"

👤

user_b

name = "Мария"
age = 30
email = "maria@example.com"

Два объекта, один класс — но совершенно разные данные. Вот в чём сила атрибутов экземпляра.

🔑 Определение: атрибут экземпляра — это переменная, привязанная к конкретному объекту. Она создаётся через синтаксис self.имя_атрибута = значение внутри методов класса.

✍️ Синтаксис: self.атрибут = значение

Чтобы создать атрибут объекта, нужно написать self.имя_атрибута = значение. Разберём каждую часть:

👁️

self

Ссылка на текущий объект. «Этому объекту» принадлежит атрибут.

🏷️

.name

Точка — оператор доступа. После точки — имя атрибута. Используй snake_case.

📝

= value

Присваивание значения. Значение приходит из параметра конструктора или задаётся напрямую.

class User:
    def __init__(self, name, age, email):
        # Создаём атрибуты и сразу присваиваем им значения из параметров
        self.name = name      # атрибут name = параметр name
        self.age = age        # атрибут age  = параметр age
        self.email = email    # атрибут email = параметр email

Обратите внимание: имя атрибута и имя параметра могут быть разными, но обычно их делают одинаковыми — это удобно и читаемо. Никто не запрещает написать self.user_name = name, если так логичнее.

🔎 Доступ к атрибутам: obj.attribute

Когда объект создан — как добраться до его данных? Всё через ту же точку: объект.атрибут.

class User:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email

# Создаём пользователей
user1 = User("Алекс", 25, "alex@example.com")
user2 = User("Мария", 30, "maria@example.com")

# Читаем атрибуты
print(user1.name)   # Алекс
print(user1.age)    # 25
print(user1.email)  # alex@example.com

print(user2.name)   # Мария
print(user2.age)    # 30

# Изменяем атрибут после создания
user1.age = 26
print(user1.age)    # 26  — теперь 26

# Атрибут user2 не изменился!
print(user2.age)    # 30  — остался 30

Атрибуты можно не только читать, но и изменять — просто присвоить новое значение через точку. При этом у другого объекта данные остаются нетронутыми.

💡 Точка — универсальный оператор. В Python точка означает «обратись к чему-то, что принадлежит этому объекту». Вы уже использовали это: "hello".upper(), [1, 2].append(3) — это тоже обращение к методам через точку. Атрибуты — то же самое, только данные, а не функции.

⚠️ Что будет если обратиться к несуществующему атрибуту?

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

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        # Заметьте: email мы НЕ добавляем

user = User("Алекс", 25)

print(user.name)   # Алекс  — OK
print(user.age)    # 25     — OK

# А теперь попробуем обратиться к email, которого нет:
print(user.email)  # AttributeError: 'User' object has no attribute 'email'

Сообщение об ошибке читается очень чётко: «объект типа 'User' не имеет атрибута 'email'». Python говорит нам точно, что пошло не так.

❌

Типичные причины AttributeError

Забыли добавить атрибут в __init__
Опечатка в имени атрибута
Обращаетесь к атрибуту другого класса

✅

Как избежать

Всегда определяй нужные атрибуты в __init__
Можно использовать hasattr(obj, 'attr') для проверки

Хорошая новость: AttributeError — это очень понятная ошибка. Как только вы её видите, сразу знаете: где-то забыли объявить атрибут или опечатались в его названии.

⚙️ Параметры со значениями по умолчанию

Иногда некоторые данные необязательны: пользователь может не указать возраст, или ему по умолчанию назначается статус «обычный». Для этого в Python есть значения по умолчанию — они работают точно так же, как в обычных функциях.

Синтаксис: параметр=значение_по_умолчанию. Если при создании объекта этот параметр не передан — используется значение по умолчанию. Если передан — используется переданное.

class User:
    def __init__(self, name, age, email="", role="user"):
        self.name = name
        self.age = age
        self.email = email   # по умолчанию — пустая строка
        self.role = role     # по умолчанию — "user"

# Обязательные параметры: name и age
# Необязательные: email и role (есть умолчания)

# Минимальный вариант — только обязательные
guest = User("Гость", 0)
print(guest.name)   # Гость
print(guest.email)  # ""  — пустая строка (умолчание)
print(guest.role)   # user

# Полный вариант — передаём всё
admin = User("Алина", 28, "alina@example.com", "admin")
print(admin.name)   # Алина
print(admin.email)  # alina@example.com
print(admin.role)   # admin

# Частичный вариант — только email, role оставим по умолчанию
member = User("Иван", 22, "ivan@example.com")
print(member.role)  # user  — умолчание

⚠️ Правило порядка: параметры со значениями по умолчанию всегда идут после обязательных параметров. Написать def __init__(self, email="", name) нельзя — Python выдаст SyntaxError. Сначала обязательные, потом необязательные.

💻 Полное демо: класс User

Соберём всё вместе в одном развёрнутом примере. Класс User с несколькими атрибутами, значениями по умолчанию, созданием нескольких объектов и работой с ними:

class User:
    def __init__(self, name, age, email="", is_active=True):
        self.name = name
        self.age = age
        self.email = email
        self.is_active = is_active

# Создаём пользователей по-разному
user1 = User("Алекс", 25, "alex@example.com")
user2 = User("Мария", 30, "maria@example.com", False)  # неактивный аккаунт
user3 = User("Гость", 0)  # только обязательные поля

# Читаем атрибуты
print(f"Пользователь: {user1.name}, {user1.age} лет")
# Пользователь: Алекс, 25 лет

print(f"Email: {user1.email}, активен: {user1.is_active}")
# Email: alex@example.com, активен: True

print(f"{user2.name} активен: {user2.is_active}")
# Мария активен: False

print(f"Гость email: '{user3.email}'")
# Гость email: ''

# Изменяем атрибуты после создания
user3.email = "guest@example.com"
print(f"Обновлённый email гостя: {user3.email}")
# Обновлённый email гостя: guest@example.com

user2.is_active = True
print(f"Мария теперь активна: {user2.is_active}")
# Мария теперь активна: True

# Каждый объект независим
user1.age = 26
print(f"Возраст Алекса: {user1.age}")   # 26
print(f"Возраст Марии: {user2.age}")    # 30 — не изменился!

Запустите этот код у себя. Поиграйте с ним: добавьте новые атрибуты в __init__, создайте больше пользователей, поизменяйте значения. Лучший способ запомнить — руки сами напишут код.

📋 Итоги урока

Сегодня вы узнали, как наполнять объекты данными с самого рождения. Вот главное:

  • Конструктор __init__ — специальный метод, который Python вызывает автоматически при создании объекта
  • Двойные подчёркивания с обеих сторон — сигнал «особый метод языка»
  • Если __init__ не определён — Python создаёт пустой объект без ошибок
  • self — первый параметр метода, ссылка на сам объект; Python передаёт его автоматически
  • Атрибут экземпляра — переменная, привязанная к конкретному объекту: self.имя = значение
  • Доступ к атрибуту снаружи: объект.атрибут
  • Атрибуты можно читать и изменять после создания объекта
  • Несуществующий атрибут → AttributeError
  • Значения по умолчанию в параметрах __init__ делают часть данных необязательными
  • Объекты одного класса полностью независимы — данные одного не влияют на другой

🚀 В следующем уроке мы детально разберём параметр self — почему он нужен, как Python использует его «под капотом», и что произойдёт, если его пропустить. Это фундаментальная концепция, которая объяснит, почему методы классов выглядят именно так.