Классы и объекты

В прошлой главе мы смотрели на класс издалека: форма, шаблон, объекты. Теперь соберём такой класс сами, строчка за строчкой.

Собираем класс по шагам

Самый короткий класс в Python выглядит так:

Python 3.13
class Person:
    pass

person = Person()
print(type(person))
<class '__main__.Person'>

class Person: объявляет класс, а Person() создаёт по нему объект. Правда, пока пустой: никаких данных внутри нет.

Данные закладывают в __init__ — специальном методе, который Python сам вызывает при каждом Person(...). Аргументы вызова попадают в его параметры:

Python 3.13
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

person = Person("Анна", 25)
print(person.name)
Анна
print(person.age)
25

Строка self.name = name читается так: «возьми параметр name и сохрани его в объекте под именем name». Слева — атрибут объекта, справа — параметр метода.

Это и есть заполнение шаблона со схемы из прошлой главы: вызов Person("Анна", 25) заполнил пропуски.

Данные есть, теперь добавим действие. Метод объявляют как обычную функцию, только внутри класса, и первым параметром он всегда принимает self:

Python 3.13
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        return f"Привет, меня зовут {self.name}, мне {self.age} лет."

person = Person("Анна", 25)
print(person.greet())
Привет, меня зовут Анна, мне 25 лет.

Заметили странность? В def greet(self) параметр объявлен, а в person.greet() мы ничего не передаём. Откуда же self берётся?

Как работает self

Метод greet записан в классе один раз, а объектов может быть сколько угодно. Значит, при вызове методу нужно как-то узнать, чей name печатать. Эту работу делает точка: вызов person.greet() Python выполняет как Person.greet(person) — объект слева от точки сам становится первым аргументом. Он и приходит в параметр self.

person.greet()Python выполняет какPerson.greet(person)
объект слева от точки становится первым аргументом и приходит в self

Проверим, что это буквально так:

Python 3.13
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        return f"Привет, меня зовут {self.name}, мне {self.age} лет."

person = Person("Анна", 25)

# Эти два вызова делают одно и то же:
print(person.greet())
Привет, меня зовут Анна, мне 25 лет.
print(Person.greet(person))
Привет, меня зовут Анна, мне 25 лет.

Раз self — обычный первый параметр, имя ему можно дать любое: def greet(this): тоже сработает. Но всё Python-сообщество пишет self, и линтеры ругаются на другие имена.

Через self методы могут вызывать другие методы того же объекта:

Python 3.13
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def is_adult(self):
        return self.age >= 18

    def describe(self):
        status = "взрослый" if self.is_adult() else "несовершеннолетний"
        return f"{self.name}: {status}"

person = Person("Анна", 25)
print(person.describe())
Анна: взрослый

Объекты в Python изменяемы

До сих пор методы только читали данные: is_adult смотрел на self.age и ничего не менял. Но состояние объекта можно и менять: методом, как add_grade ниже, или присваиванием атрибуту напрямую.

Python 3.13
class Student:
    def __init__(self, name):
        self.name = name
        self.grades = []

    def add_grade(self, grade):
        self.grades.append(grade)
        return f"Добавлена оценка: {grade}"

    def average_grade(self):
        if not self.grades:
            return "Нет оценок"
        return round(sum(self.grades) / len(self.grades), 1)

student = Student("Мария")
print(f"Средний балл: {student.average_grade()}")
Средний балл: Нет оценок
print(student.add_grade(5))
Добавлена оценка: 5
print(student.add_grade(4))
Добавлена оценка: 4
print(student.add_grade(5))
Добавлена оценка: 5
print(f"Средний балл: {student.average_grade()}")
Средний балл: 4.7

Метод add_grade меняет self.grades — список, хранящийся в объекте. Изменения происходят на месте: следующий вызов student.average_grade() видит обновлённое состояние. Это не «верни новый список», а «измени существующий».

Динамические атрибуты

Менять существующие атрибуты — обычное дело. Python разрешает больше: объекту можно добавить любой новый атрибут в любой момент, даже не объявленный в __init__. Проверим на упрощённом Student, у которого есть только имя:

Python 3.13
class Student:
    def __init__(self, name):
        self.name = name

student = Student("Мария")
student.age = 19            # добавили новый атрибут на лету
student.favorite_color = "синий"

print(student.age)
19
print(student.favorite_color)
синий

Технически это работает, но в реальном коде так почти не пишут, и вот почему:

  • Состояние объекта становится непредсказуемым. Глядя на класс Student, нельзя понять, какие атрибуты в действительности есть у объекта.
  • IDE и линтеры не помогут с автодополнением: они знают только то, что объявлено в __init__.
  • При опечатке создастся новый атрибут вместо понятной ошибки. Если вы напишете student.aeg = 19 вместо student.age = 19, Python молча создаст новое поле aeg, и баг сложно найти.

Поэтому все атрибуты объекта объявляйте в __init__ — даже со значением None, если они появятся позже:

Python 3.13
class Student:
    def __init__(self, name):
        self.name = name
        self.age = None      # будет заполнено позже
        self.grades = []

Так класс честно описывает, какие поля есть у объекта, и опечатки сразу превращаются в AttributeError.

Проверка понимания

Что произойдёт при выполнении Person.greet(person), если greet определён с self как первый параметр?

В следующей статье разберём атрибуты подробнее: чем атрибуты экземпляра отличаются от атрибутов класса и как не наступить на классическую ловушку с изменяемыми значениями по умолчанию в __init__.