ООП в Python — парадигма разработки, при которой программа строится из объектов: самодостаточных единиц с данными и поведением. Если вы освоили основы Python — переменные, функции, циклы — эта статья покажет, как перейти на следующий уровень и начать писать структурированный код.
Разберём, что такое класс и объект, как работают четыре принципа объектно-ориентированного программирования — инкапсуляция, наследование, полиморфизм и абстракция. Объясним магические методы, разберём, что такое порядок разрешения методов (MRO) и как он работает при множественном наследовании. В конце — три практические задачи для закрепления: с начального уровня до продвинутого.
Учитесь бесплатно за счёт государства
Экономия до 100 000 ₽ на любой программе
Объектно-ориентированное программирование (ООП) — парадигма, при которой программа строится вокруг объектов, а не вокруг последовательности команд. Каждый объект — контейнер: он хранит данные в атрибутах и определяет поведение через методы.
В Python всё является объектом. Целые числа, строки, списки, сами функции и классы — объекты с атрибутами и методами. Когда вы пишете "hello".upper(), вы вызываете метод объекта-строки.
Класс — шаблон: описывает, какие атрибуты и методы будут у каждого объекта. Объект — конкретный экземпляр по этому шаблону. Один класс Book порождает тысячи объектов с разными названиями и авторами, но единой структурой.
ООП особенно полезен в больших проектах: код становится модульным, его проще сопровождать и расширять. Большинство популярных Python-фреймворков — Django, Flask, SQLAlchemy — построены на классах.

Представьте кухню, где всё лежит вразнобой: ножи рядом с крупами, соль в холодильнике. Формально ничего не потеряно — но найти нужное трудно, и стоит сдвинуть один предмет, как что-то рассыпается.
Процедурный код устроен похоже. Функции обращаются к глобальным переменным, вызывают друг друга через всю программу. Меняете логику в одном месте — неожиданно ломается другое. Дублирование растёт: одни и те же проверки копируются из функции в функцию, зависимости между частями кода превращаются в клубок, который почти невозможно распутать.
ООП решает проблему по-другому: каждый объект отвечает за своё состояние и поведение. Изменения локализованы — правите один класс, не затрагивая остальные. Код становится предсказуемым и читаемым.

Класс — это чертёж, объект — дом, построенный по этому чертёжу. Создать класс в Python просто:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
Чтобы получить объект, класс вызывают как функцию:
book1 = Book("Три товарища", "Ремарк")
book2 = Book("Мастер и Маргарита", "Булгаков")
book1 и book2 — два независимых экземпляра одного класса. Каждый хранит свои данные в __dict__, но использует общие методы класса. Один шаблон, разные объекты.
Атрибуты бывают двух видов. Динамические принадлежат конкретному экземпляру — задаются через self в методе __init__:
self.title = title # у каждого объекта своё значение
Статические принадлежат классу и разделяются между всеми экземплярами:
class Drink:
volume = 200 # статический — один для всех
def __init__(self, name):
self.name = name # динамический — у каждого свой
Изменение Drink.volume повлияет на все объекты, у которых нет собственного атрибута volume. Особенно важно помнить об этом, когда статический атрибут — изменяемый тип: список или словарь. Ошибка здесь приводит к неожиданным побочным эффектам.
Метод — функция внутри класса. Первый параметр — всегда self, ссылка на текущий экземпляр. Через self метод обращается к атрибутам именно этого объекта, а не какого-то другого.
class Book:
def describe(self):
return f"{self.title} — {self.author}"
Вызов book1.describe() — Python фактически выполняет Book.describe(book1), передавая объект первым аргументом. В Java и C++ аналог self называется this. Атрибуты — состояние объекта, методы — действия над этим состоянием.
Объектно-ориентированное программирование строится на четырёх принципах: инкапсуляции, наследовании, полиморфизме и абстракции. Каждый решает конкретную задачу: защита данных от некорректного изменения, повторное использование кода без дублирования, единый интерфейс для объектов разных типов, контракт между базовым и дочерним классами. Разберём каждый принцип на конкретном примере с кодом.
Инкапсуляция — ограничение прямого доступа к данным объекта. Цель: защитить состояние от некорректного изменения снаружи.
В Python три уровня доступа — не жёсткий механизм языка, а соглашение между разработчиками:
| Запись | Уровень | Поведение |
|---|---|---|
attr |
публичный | доступен откуда угодно |
_attr |
защищённый | соглашение «не обращайся снаружи» |
__attr |
псевдоприватный | искажение имён (name mangling): переименовывается в _ClassName__attr |
Таблица 1. Уровни доступа к атрибутам объекта в Python. Источник: официальная документация Python 3.
Искажение имён для __attr защищает от случайного переопределения в подклассах, но не создаёт настоящей приватности — обратиться через obj._ClassName__attr технически возможно. В Python нет жёсткого private как в Java: принцип «мы все взрослые».
Правильный способ контролировать доступ — декоратор @property:
class Person:
def __init__(self, age):
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Возраст не может быть отрицательным")
self._age = value
Теперь person.age = -5 вызовет ValueError вместо молчаливого сохранения невалидного значения. @property реализует инкапсуляцию через протокол дескрипторов: атрибут выглядит как обычный, но доступ к нему проходит через функции.

Наследование позволяет дочернему классу получить атрибуты и методы родительского без дублирования кода.
class EBook(Book):
def __init__(self, title, author, format_type):
super().__init__(title, author)
self.format_type = format_type
super().__init__(title, author) вызывает конструктор родителя. Без этого вызова атрибуты title и author не инициализируются — и последующие обращения к ним упадут с AttributeError.
MRO — порядок разрешения методов (Method Resolution Order) — определяет, в каком порядке Python ищет метод при вызове. Просмотреть: ClassName.__mro__. При множественном наследовании порядок вычисляет алгоритм C3:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
# D.__mro__ == (D, B, C, A, object)
Алгоритм C3 введён в Python 2.3. Он гарантирует предсказуемый порядок и решает проблему ромбовидного наследования: каждый базовый класс встречается в цепочке ровно один раз. super() делегирует не прямому родителю, а следующему по MRO — при множественном наследовании это критично.
Главный смысл наследования — принцип DRY (Don’t Repeat Yourself, «не повторяйся»): общая логика живёт в родительском классе, дочерние добавляют только уникальное.
Полиморфизм — один интерфейс, разные реализации. Дочерний класс переопределяет метод родителя:
class Book:
def describe(self):
return f"{self.title} — {self.author}"
class EBook(Book):
def describe(self):
return f"{self.title} (электронная, {self.format_type})"
Один вызов obj.describe() даёт разный результат в зависимости от типа объекта.
Ещё глубже полиморфизм проявляется через утиную типизацию (duck typing): «если объект ведёт себя как утка — он утка». Не важно, какой класс — важно, есть ли у объекта нужный метод:
def show_info(obj):
print(obj.describe())
show_info(Book("Преступление и наказание", "Достоевский"))
show_info(EBook("Дюна", "Херберт", "epub"))
Функция show_info работает с любым объектом, у которого есть метод describe(), — без isinstance() и без цепочки if-else. Единый API для объектов разных типов.
Абстракция — выделение существенных характеристик объекта и сокрытие деталей реализации. В Python абстрактные классы создают через модуль abc:
from abc import ABC, abstractmethod
class Character(ABC):
@abstractmethod
def attack(self):
pass
class Archer(Character):
def attack(self):
return "Выпускает стрелу"
Создать экземпляр Character напрямую нельзя — Python выбросит TypeError: Can't instantiate abstract class Character with abstract method attack. Наследник обязан реализовать все методы с @abstractmethod, иначе сам становится абстрактным.
Абстракция фиксирует контракт: «любой персонаж умеет атаковать» — и оставляет конкретную реализацию дочерним классам.
Магические методы — методы с двойным подчёркиванием в начале и конце имени (от англ. dunder, сокращение от double underscore). Интерпретатор вызывает их автоматически в ответ на определённые операции — вы не вызываете их напрямую. Именно они стоят за тем, почему print(obj) работает, почему объект можно сравнить через == и почему len() знает его длину.
| Метод | Когда вызывается | Пример вызова |
|---|---|---|
__init__ |
создание объекта | Book(«Дюна», «Херберт») |
__str__ |
print(obj), str(obj) | print(book) |
__repr__ |
интерактивная консоль, repr(obj) | repr(book) |
__eq__ |
оператор == | book1 == book2 |
__len__ |
встроенная len(obj) | len(library) |
__add__ |
оператор + | vec1 + vec2 |
__iter__ |
цикл for, iter(obj) | for item in obj |
__getitem__ |
обращение по индексу | obj[0] |
Таблица 2. Топ-8 магических методов Python: когда вызываются и как.
__init__ — не конструктор в классическом смысле: объект уже создан к моменту его вызова. Это инициализатор — задаёт начальное состояние через self.attr = value.
__str__ возвращает читаемую строку для пользователя: вызывается через print(obj) и str(obj).
__repr__ возвращает техническое представление — для отладки и интерактивной консоли. Хороший __repr__ — строка, по которой можно воссоздать объект:
class Book:
def __str__(self):
return self.title
def __repr__(self):
return f"Book(title={self.title!r}, author={self.author!r})"
Если __str__ не определён, Python использует __repr__ как запасной вариант. Минимум для любого класса — определить хотя бы __repr__: это делает отладку значительно проще.
Хотите сменить профессию или повысить квалификацию?
Федеральный проект «Активные меры содействия занятости» даёт возможность пройти обучение бесплатно за счёт государства
__len__ добавляет поддержку встроенной функции len(obj) — метод должен возвращать неотрицательное целое число:
class Library:
def __len__(self):
return len(self.books)
__eq__ переопределяет оператор ==. По умолчанию два объекта равны только если это один и тот же объект в памяти. С __eq__ сравнивают по содержимому:
def __eq__(self, other):
return self.title == other.title and self.author == other.author
Через dunder-методы объект ведёт себя как встроенный тип — это называют перегрузкой операторов. __add__ реализует +, __iter__ делает объект итерируемым, __enter__/__exit__ поддерживают менеджер контекста with. Именно поэтому числа, строки и списки в Python работают так единообразно.

После освоения четырёх принципов — следующий уровень: классы данных с @dataclass, метаклассы и детали работы MRO. Эти инструменты часто встречаются в реальных Python-проектах и кодовых базах популярных фреймворков. Знание этих концепций отличает начинающего разработчика от специалиста среднего уровня.
Классы, созданные только для хранения данных, часто содержат однообразный __init__. Декоратор @dataclass (Python 3.7+, PEP 557) генерирует его автоматически вместе с __repr__ и __eq__:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
Эти три строки заменяют десять — без ручного написания __init__, __repr__ и __eq__.
Когда использовать: класс хранит данные, логика минимальна или отсутствует. Когда не использовать: нужен нетривиальный __init__ с валидацией или сложная бизнес-логика — тогда лучше явно описать методы.
В Python класс — тоже объект. Его тип — type. Именно type создаёт классы так же, как классы создают объекты:
# Эти два определения эквивалентны:
class Foo:
x = 10
Foo = type("Foo", (), {"x": 10})
type(obj) возвращает тип объекта. type(name, bases, dict) создаёт класс динамически.
Кастомный метакласс задаётся через metaclass=:
class MyMeta(type):
pass
class Foo(metaclass=MyMeta):
pass
На практике метаклассы нужны редко — для фреймворков, систем объектно-реляционного отображения (ORM) и автоматической валидации при создании класса. При конфликте метаклассов у базовых классов Python выбрасывает ошибку.
MRO определяет цепочку поиска метода: экземпляр → его класс → родители по MRO → object. Просмотреть порядок: ClassName.__mro__.
Алгоритм C3 (Python 2.3+) вычисляет этот порядок детерминированно. Для ромбовидного наследования:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
# D.__mro__ == (D, B, C, A, object)
super() — прокси-объект, который делегирует вызов следующему в MRO, а не прямому родителю. При множественном наследовании это гарантирует: каждый __init__ в цепочке вызывается ровно один раз, без дублирования и пропусков.

Теория закрепляется только практикой. Вот три задачи с возрастающей сложностью.
Задача 1 — начинающий: наследование
Создайте класс Animal с атрибутом name и методом speak(), возвращающим строку «Звук». Создайте дочерний класс Dog, который переопределяет speak() и возвращает «Гав». Проверьте, что Dog("Шарик").speak() возвращает правильную строку.
Подсказка: вызовите super().__init__(name) в конструкторе Dog.
Задача 2 — средний: инкапсуляция
Создайте класс BankAccount с атрибутом _balance. Реализуйте @property balance с геттером и сеттером. Сеттер должен запрещать отрицательный баланс и выбрасывать ValueError с понятным сообщением.
Подсказка: декоратор @balance.setter принимает self и value. Проверяйте value до присвоения.
Задача 3 — продвинутый: магические методы
Создайте класс Rectangle с атрибутами width и height. Реализуйте __str__ (читаемое описание), __repr__ (техническое представление), __eq__ (сравнение по площади) и __len__ (возвращает периметр как целое число).
Подсказка: __len__ должен возвращать int — при необходимости приводите через int(...).
Для системного изучения подойдут несколько форматов. Официальная документация Python содержит подробный раздел про классы — docs.python.org/3/tutorial/classes.html. Для практики с автопроверкой — задачи на LeetCode и Codewars, которые удобно решать с ООП-подходом. Структурированные курсы с тренажёрами ускоряют освоение: вы сразу пишете код, получаете обратную связь и движетесь по выстроенной программе.

В рамках федерального проекта «Активные меры содействия занятости» доступны бесплатные ИТ-программы с нуля — за счёт государства. Например, «Специалист по информационным системам: от организации до сопровождения ИТ-проектов» — 144 часа, онлайн-формат, обучение совмещается с работой. Смотрите каталог доступных программ.

Класс — шаблон (чертёж), объект — конкретный экземпляр по этому шаблону. Например, Book — класс, а book1 = Book("Три товарища", "Ремарк") — объект. У одного класса может быть неограниченное количество объектов с разными данными, но общей структурой. Технически: класс — объект типа type; каждый объект хранит свои данные в __dict__.
self — ссылка на текущий экземпляр класса, первый параметр любого метода экземпляра. Через self метод получает доступ к атрибутам конкретного объекта. Аналог в Java и C++ — this. При вызове book.describe() Python фактически выполняет Book.describe(book), передавая объект первым аргументом.
Оба возвращают строку, но для разных целей. __str__ — читаемое представление для print() и конечного пользователя. __repr__ — техническое, для отладки и интерактивной консоли; хорошее __repr__ позволяет воссоздать объект по строке. Если __str__ не определён, Python использует __repr__ как запасной вариант.
Атрибуты с двойным подчёркиванием (__attr) интерпретатор переименовывает в _ClassName__attr. Это защищает от случайного переопределения имён в подклассах, но не создаёт настоящей приватности — доступ через obj._ClassName__attr технически возможен. В Python нет жёсткого private как в Java.
@dataclass удобен, когда класс нужен только для хранения данных без сложной логики — декоратор автоматически генерирует __init__, __repr__ и __eq__. Если класс содержит нетривиальную валидацию или бизнес-логику, лучше обычный класс с явно описанными методами.
Утиная типизация (duck typing) — принцип Python: «если объект ведёт себя как утка — он утка». Не важно, от какого класса наследован объект; важно, есть ли у него нужный метод. Это основа полиморфизма в Python: функция вызывает obj.describe() и работает с любым объектом, у которого этот метод есть, — без isinstance().
Нет. В Python нет механизма настоящего private как в Java. Соглашение _attr означает «внутренний, не трогай». Двойное __attr включает искажение имён, усложняя случайный доступ, но обратиться через _ClassName__attr всё равно можно.
Паттерны — типовые решения архитектурных задач: Singleton (один экземпляр класса в программе), Factory (фабрика объектов), Observer (подписка на события). Все реализуются через классы и принципы ООП. Изучать паттерны стоит после уверенного владения наследованием, инкапсуляцией и полиморфизмом.
Для начинающих подойдут структурированные курсы с тренажёрами и автопроверкой задач. Официальная документация Python содержит подробный раздел про классы (docs.python.org/3/tutorial/classes.html). Задачи на LeetCode и Codewars удобно решать с ООП-подходом. Для системного освоения ИТ-навыков с нуля — в рамках нацпроекта «Кадры» доступны бесплатные программы по ИТ.
Для базового уровня: «Изучаем Python» Марка Лутца — подробный разбор классов и ООП. Для среднего: «Чистый Python» Дэна Бейдера — практические паттерны и идиомы. Для продвинутых: «Fluent Python» Лусиано Рамальо — глубокий разбор дескрипторов, метаклассов и магических методов. Все три доступны в русском переводе.
Подайте заявку —
забронируйте место в группе
45 000 мест на 2026 год. Бесплатное обучение по федеральному проекту «Активные меры содействия занятости»