Вы уже умеете писать функции, использовать списки и словари, обрабатывать ошибки и даже собирать код в модули. Но есть одна вещь, которая отличает скрипты от настоящих программ — объектно-ориентированное программирование (ООП). Сегодня мы познакомимся с классами и объектами. Не пугайтесь — это просто способ упаковать данные и действия в одну красивую коробку.
1. Почему функций уже недостаточно?
Представьте, что вы пишете программу для учёта студентов. У каждого студента есть имя, возраст, оценки. Без классов вам придётся хранить три списка и передавать их в каждую функцию:
names = ["Анна", "Иван"]
ages = [20, 21]
grades = [[5,4,5], [3,4,4]]
def add_grade(name, grade):
# найти индекс имени, потом добавить оценку...
Это работает, но становится неудобно, когда данных много. Класс позволяет объединить имя, возраст, оценки и методы работы с ними в одну сущность — объект.
2. Класс — это чертёж, объект — конкретный экземпляр
- Класс — как форма для выпечки. Она описывает, какой формы будут печенья, но сама по себе не съедобна.
- Объект — конкретное печенье, созданное по этой форме.
Синтаксис класса:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
print(f"Привет, я {self.name}, мне {self.age} лет.")
Разберём:
- __init__ — конструктор, вызывается при создании объекта. self — ссылка на сам объект.
- self.name = name — сохраняем данные внутрь объекта.
- say_hello — метод (функция внутри класса).
Создаём объекты:
student1 = Student("Анна", 20)
student2 = Student("Иван", 21)
student1.say_hello() # Привет, я Анна, мне 20 лет.
student2.say_hello() # Привет, я Иван, мне 21 год.
3. Атрибуты и методы
- Атрибуты — данные, принадлежащие объекту (например, name, age).
- Методы — функции, работающие с этими данными.
Добавим метод для добавления оценки:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
self.grades = [] # список оценок
def add_grade(self, grade):
self.grades.append(grade)
def average_grade(self):
if not self.grades:
return 0
return sum(self.grades) / len(self.grades)
Использование:
s = Student("Анна", 20)
s.add_grade(5)
s.add_grade(4)
print(s.average_grade()) # 4.5
4. Магия self и почему он нужен
self — это первый параметр любого метода. Когда вы пишете s.add_grade(5), Python автоматически передаёт s как self. Не называйте его по-другому (хотя технически можно), это общепринятое имя.
5. Атрибуты класса vs атрибуты объекта
Иногда нужно, чтобы все объекты делили одно значение. Например, название школы. Для этого используют атрибуты класса:
class Student:
school = "Школа №1" # атрибут класса
def __init__(self, name):
self.name = name # атрибут объекта
print(Student.school) # Школа №1
s = Student("Анна")
print(s.school) # Школа №1
s.school = "Школа №2" # создаст атрибут объекта, не трогая класс
print(Student.school) # всё ещё Школа №1
6. Инкапсуляция (скрытие данных)
В Python нет строгих приватных атрибутов, но есть соглашение: если имя начинается с подчёркивания _ — не трогайте его извне. Двойное подчёркивание __ включает механизм name mangling (усложняет доступ).
class BankAccount:
def __init__(self, balance):
self._balance = balance # защищённый (не лезь без нужды)
def deposit(self, amount):
self._balance += amount
def get_balance(self):
return self._balance
7. Типичные ошибки новичков
❌ Забыли self в параметрах метода
def say_hello(): # нет self
print(f"Привет, {self.name}") # NameError
❌ Пытаемся вызвать метод без создания объекта
Student.say_hello() # TypeError: missing required argument 'self'
❌ Путаем атрибуты класса и объекта
Изменение атрибута класса через объект создаёт новый атрибут объекта, а не меняет класс.
❌ Не используем __init__ для инициализации
Лучше задать все атрибуты в конструкторе, а не добавлять их «на лету» после создания.
8. Живой пример: телефонная книга в стиле ООП
Перепишем нашу телефонную книгу с классами:
class Contact:
def __init__(self, name, phone):
self.name = name
self.phone = phone
def __str__(self):
return f"{self.name} — {self.phone}"
class PhoneBook:
def __init__(self):
self.contacts = []
def add(self, name, phone):
self.contacts.append(Contact(name, phone))
def find(self, name):
found = [c for c in self.contacts if c.name.lower() == name.lower()]
return found
def show_all(self):
for c in self.contacts:
print(c)
def save_to_file(self, filename):
import json
data = [{"name": c.name, "phone": c.phone} for c in self.contacts]
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
def load_from_file(self, filename):
import json
import os
if not os.path.exists(filename):
return
with open(filename, "r", encoding="utf-8") as f:
data = json.load(f)
self.contacts = [Contact(d["name"], d["phone"]) for d in data]
# Использование
pb = PhoneBook()
pb.load_from_file("contacts.json")
pb.add("Анна", "123-456")
pb.show_all()
pb.save_to_file("contacts.json")
Теперь код стал намного понятнее: PhoneBook управляет списком контактов, Contact представляет один контакт.
Заключение
Классы — это не магия, а удобный способ группировать данные и функции. Вы узнали:
- как объявить класс и создать объект,
- зачем нужен __init__ и self,
- разницу между атрибутами объекта и класса,
- основы инкапсуляции.
Следующая статья будет про наследование — когда один класс берёт свойства другого, позволяя переиспользовать код ещё эффективнее. А пока — попробуйте описать классом что-то из реального мира: книгу, автомобиль, рецепт. Это лучший способ привыкнуть к ООП.
Делитесь в комментариях, какой класс вы написали первым и что в нём хранили.
Статья подготовлена для канала «Код как искусство». Подписывайтесь, чтобы писать код, который думает об объектах.