- ООП строит программу из объектов: каждый хранит данные и умеет выполнять действия над ними.
- Класс — шаблон, объект — его экземпляр; наследование переиспользует код, полиморфизм позволяет вызывать одно и то же по-разному.
- В Python нет по-настоящему закрытых атрибутов: подчёркивание в начале имени — соглашение.
- Классы нужны не всегда: для коротких скриптов часто хватает функций и словарей.
Содержание11 разделов ▾
Объектно-ориентированное программирование (ООП) — это способ писать программу как набор объектов: каждый объект хранит свои данные и умеет выполнять действия над ними. Статья для тех, кто знает в Python переменные, функции и списки. Вы напишете три класса и увидите на запускаемом примере, что такое наследование и полиморфизм.
Определения сверены с официальным учебником Python и Википедией. Код проверен запуском на Python 3.13. Данные на октябрь 2026 года.
Зачем нужны классы и объекты
Представьте, что нужно считать площади разных фигур. Без классов можно хранить фигуру в словаре и написать функцию с цепочкой if:
import math
def площадь(фигура):
if фигура["вид"] == "круг":
return math.pi * фигура["r"] ** 2
elif фигура["вид"] == "прямоугольник":
return фигура["a"] * фигура["b"]
raise ValueError("неизвестная фигура")
print(площадь({"вид": "круг", "r": 2}))
print(площадь({"вид": "прямоугольник", "a": 3, "b": 4}))12.566370614359172
12Это работает, пока фигур две. Когда появится третья, придётся лезть в функцию и добавлять ещё одну ветку. С десятью фигурами функция превратится в длинную цепочку, в которой легко ошибиться. Классы решают эту задачу иначе: каждая фигура знает, как посчитать свою площадь.
Класс и объект в Python
Учебник Python определяет классы как средство связать данные и функциональность вместе. Создание нового класса создаёт новый тип объектов, и можно делать экземпляры этого типа. У каждого экземпляра могут быть атрибуты, в которых хранится состояние, и методы, которые это состояние изменяют.
import math
class Круг:
def __init__(self, r):
self.r = r
def площадь(self):
return math.pi * self.r ** 2
к = Круг(2)
print(к.r, round(к.площадь(), 2))2 12.57Метод __init__ вызывается при создании объекта и задаёт начальные значения. Первый параметр каждого метода, по соглашению self, — это сам объект: через него метод обращается к его данным. Вызывая Круг(2), мы создаём объект с радиусом 2, а запись к.площадь() вызывает метод у этого объекта.
Инкапсуляция
Инкапсуляция означает, что данные и действия над ними собраны в одном месте, а детали можно не показывать снаружи. В классе Круг радиус и расчёт площади лежат рядом, и пользователю класса не нужно знать формулу.
Наследование
Наследование позволяет создать новый класс на основе существующего. Учебник показывает синтаксис class Производный(Базовый). Производный класс получает атрибуты и методы базового и может переопределить их. Обратиться к базовому классу из производного можно через super().
Сделаем общий класс Фигура и два наследника.
import math
class Фигура:
def __init__(self, название):
self.название = название
def площадь(self):
raise NotImplementedError("в наследнике нужно переопределить площадь()")
def описание(self):
return f"{self.название}: площадь {self.площадь():.2f}"
class Круг(Фигура):
def __init__(self, r):
super().__init__("круг")
self.r = r
def площадь(self):
return math.pi * self.r ** 2
class Прямоугольник(Фигура):
def __init__(self, a, b):
super().__init__("прямоугольник")
self.a, self.b = a, b
def площадь(self):
return self.a * self.b
class Квадрат(Прямоугольник):
def __init__(self, сторона):
super().__init__(сторона, сторона)
self.название = "квадрат"Метод описание написан один раз в базовом классе, а работает для всех наследников. Квадрат наследуется от Прямоугольника и вообще не определяет площадь: он берёт её у родителя.
Полиморфизм
Полиморфизм здесь выглядит так: мы вызываем одно и то же действие у объектов разных классов, а каждый выполняет его по-своему.
фигуры = [Круг(2), Прямоугольник(3, 4), Квадрат(5)]
for ф in фигуры:
print(ф.описание())
print(isinstance(фигуры[2], Прямоугольник), isinstance(фигуры[2], Фигура), isinstance(фигуры[0], Прямоугольник))
try:
Фигура("абстрактная").описание()
except NotImplementedError as e:
print("Ошибка:", e)круг: площадь 12.57
прямоугольник: площадь 12.00
квадрат: площадь 25.00
True True False
Ошибка: в наследнике нужно переопределить площадь()Цикл не проверяет, какая перед ним фигура. Чтобы добавить новую фигуру, достаточно написать ещё один класс: код цикла и метод описание не меняются. Функция isinstance из документации проверяет, что объект принадлежит классу или его наследнику: квадрат одновременно Прямоугольник и Фигура, но не наоборот. Исключение NotImplementedError напоминает, что у «общей» фигуры площади нет: её должен определить наследник.
Абстрактный класс
Чтобы запретить создавать «общую» фигуру совсем, в Python есть модуль abc. Документация описывает его как инфраструктуру для абстрактных базовых классов. Для начала достаточно знать, что такой класс нельзя создать напрямую, а наследники обязаны реализовать нужные методы. Подробности — в документации модуля.
Наследование или композиция
Наследование выражает отношение «является»: квадрат является прямоугольником. Композиция выражает отношение «имеет»: автомобиль имеет двигатель.
class Двигатель:
def запустить(self):
return "двигатель работает"
class Автомобиль:
def __init__(self):
self.двигатель = Двигатель()
def поехать(self):
return self.двигатель.запустить() + ", едем"
print(Автомобиль().поехать())двигатель работает, едемАвтомобиль не наследуется от двигателя, а содержит его внутри.
| Ситуация | Подход |
|---|---|
| Один объект является частным случаем другого | наследование |
| Один объект состоит из других или использует их | композиция |
| Наследование приводит к цепочке из нескольких уровней | подумайте о композиции |
Когда обойтись без классов
Классы не обязательны. Первый пример этой статьи обошёлся без классов, а Википедия отмечает, что ООП нельзя считать лучшей парадигмой для всех задач.
| Задача | Чем обойтись |
|---|---|
| Короткий скрипт, несколько шагов | функции |
| Набор данных без поведения | словарь или dataclass |
| Данные и действия над ними идут вместе, видов несколько | класс |
Модуль dataclasses из документации автоматически добавляет в класс служебные методы вроде __init__ и __repr__:
from dataclasses import dataclass
@dataclass
class Точка:
x: float
y: float
print(Точка(1, 2))
print(Точка(1, 2) == Точка(1, 2))Точка(x=1, y=2)
TrueУсловия и ограничения
- Версия
- Python 3.13, октябрь 2026 — код проверен запуском; ссылки на документацию ведут на актуальную версию учебника.
- Язык
- Примеры на Python — в C++, Java и C# синтаксис и правила видимости отличаются, идея та же.
- Уровень
- Без сложных тем — множественное наследование, метаклассы и свойства в статье не разбираются.
- Данные
- Учебные примеры — фигуры с небольшими числами, без файлов и сети.
Частые ошибки
- Забывают
selfв определении метода или при обращении к атрибуту. - Хранят изменяемые данные в атрибуте класса вместо атрибута объекта (см. ниже).
- Строят цепочки наследования из пяти-шести уровней, где проще композиция.
- Наследуют, когда нет отношения «является».
- Путают класс и объект:
Круг— шаблон,Круг(2)— экземпляр.
Про изменяемый атрибут класса. Если список создан в теле класса, он общий для всех объектов:
class Корзина:
товары = []
def добавить(self, т):
self.товары.append(т)
a, b = Корзина(), Корзина()
a.добавить("хлеб")
print(b.товары)['хлеб']Хлеб оказался в корзине b, хотя добавляли его в a. Правильно создавать список в __init__: self.товары = []. Тогда у каждого объекта свой список.
Короткий вывод
ООП собирает данные и действия над ними в объекты, а классы служат шаблонами для этих объектов. Наследование помогает не повторять код, полиморфизм позволяет работать с разными объектами одинаково. Если задача небольшая, хватит функций и словарей, а класс стоит заводить тогда, когда он упрощает код.




