Конструктор __init__() и атрибуты
В прошлом уроке мы создавали объекты — но они были пустыми, без каких-либо данных внутри. Сегодня мы научимся «наполнять» объекты при рождении: задавать имя, возраст, email — всё что нужно. Познакомимся с конструктором и атрибутами.
🤔 Почему пустых объектов недостаточно?
В прошлом уроке мы написали класс 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 использует его «под капотом», и что произойдёт, если его пропустить. Это фундаментальная концепция, которая объяснит, почему методы классов выглядят именно так.