Классы и объекты
В прошлой главе мы смотрели на класс издалека: форма, шаблон, объекты. Теперь соберём такой класс сами, строчка за строчкой.
Собираем класс по шагам
Самый короткий класс в Python выглядит так:
Python 3.13class Person: pass person = Person() print(type(person))<class '__main__.Person'>
class Person: объявляет класс, а Person() создаёт по нему объект. Правда, пока пустой: никаких данных внутри нет.
Данные закладывают в __init__ — специальном методе, который Python сам вызывает при каждом Person(...). Аргументы вызова попадают в его параметры:
Python 3.13class 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.13class 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.
selfПроверим, что это буквально так:
Python 3.13class 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.13class 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.13class 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))Добавлена оценка: 5print(student.add_grade(4))Добавлена оценка: 4print(student.add_grade(5))Добавлена оценка: 5print(f"Средний балл: {student.average_grade()}")Средний балл: 4.7
Метод add_grade меняет self.grades — список, хранящийся в объекте. Изменения происходят на месте: следующий вызов student.average_grade() видит обновлённое состояние. Это не «верни новый список», а «измени существующий».
Динамические атрибуты
Менять существующие атрибуты — обычное дело. Python разрешает больше: объекту можно добавить любой новый атрибут в любой момент, даже не объявленный в __init__. Проверим на упрощённом Student, у которого есть только имя:
Python 3.13class Student: def __init__(self, name): self.name = name student = Student("Мария") student.age = 19 # добавили новый атрибут на лету student.favorite_color = "синий" print(student.age)19print(student.favorite_color)синий
Технически это работает, но в реальном коде так почти не пишут, и вот почему:
- Состояние объекта становится непредсказуемым. Глядя на класс Student, нельзя понять, какие атрибуты в действительности есть у объекта.
- IDE и линтеры не помогут с автодополнением: они знают только то, что объявлено в __init__.
- При опечатке создастся новый атрибут вместо понятной ошибки. Если вы напишете student.aeg = 19 вместо student.age = 19, Python молча создаст новое поле aeg, и баг сложно найти.
Поэтому все атрибуты объекта объявляйте в __init__ — даже со значением None, если они появятся позже:
Python 3.13class Student: def __init__(self, name): self.name = name self.age = None # будет заполнено позже self.grades = []
Так класс честно описывает, какие поля есть у объекта, и опечатки сразу превращаются в AttributeError.
Проверка понимания
Что произойдёт при выполнении Person.greet(person), если greet определён с self как первый параметр?
В следующей статье разберём атрибуты подробнее: чем атрибуты экземпляра отличаются от атрибутов класса и как не наступить на классическую ловушку с изменяемыми значениями по умолчанию в __init__.
