Медиаблог /

ООП в Python: классы, объекты и принципы с нуля

13 августа 2026

ООП в Python: классы, объекты и принципы с нуля

ООП в Python — парадигма разработки, при которой программа строится из объектов: самодостаточных единиц с данными и поведением. Если вы освоили основы Python — переменные, функции, циклы — эта статья покажет, как перейти на следующий уровень и начать писать структурированный код.

ООП в Python — класс как чертёж создаёт объект-дом

Разберём, что такое класс и объект, как работают четыре принципа объектно-ориентированного программирования — инкапсуляция, наследование, полиморфизм и абстракция. Объясним магические методы, разберём, что такое порядок разрешения методов (MRO) и как он работает при множественном наследовании. В конце — три практические задачи для закрепления: с начального уровня до продвинутого.

image

Учитесь бесплатно за счёт государства

Экономия до 100 000 ₽ на любой программе

Выбрать курс

Что такое ООП в Python и зачем он нужен разработчику

Объектно-ориентированное программирование (ООП) — парадигма, при которой программа строится вокруг объектов, а не вокруг последовательности команд. Каждый объект — контейнер: он хранит данные в атрибутах и определяет поведение через методы.

В Python всё является объектом. Целые числа, строки, списки, сами функции и классы — объекты с атрибутами и методами. Когда вы пишете "hello".upper(), вы вызываете метод объекта-строки.

Класс — шаблон: описывает, какие атрибуты и методы будут у каждого объекта. Объект — конкретный экземпляр по этому шаблону. Один класс Book порождает тысячи объектов с разными названиями и авторами, но единой структурой.

ООП особенно полезен в больших проектах: код становится модульным, его проще сопровождать и расширять. Большинство популярных Python-фреймворков — Django, Flask, SQLAlchemy — построены на классах.

Процедурный подход против ООП: почему код превращается в «спагетти»

Схема зависимостей: процедурный подход против ООП в Python

Представьте кухню, где всё лежит вразнобой: ножи рядом с крупами, соль в холодильнике. Формально ничего не потеряно — но найти нужное трудно, и стоит сдвинуть один предмет, как что-то рассыпается.

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

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

Классы и объекты в Python: синтаксис и ключевые элементы

Диаграмма класса Python с атрибутами и методами

Класс — это чертёж, объект — дом, построенный по этому чертёжу. Создать класс в Python просто:

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author

Чтобы получить объект, класс вызывают как функцию:

book1 = Book("Три товарища", "Ремарк")
book2 = Book("Мастер и Маргарита", "Булгаков")

book1 и book2 — два независимых экземпляра одного класса. Каждый хранит свои данные в __dict__, но использует общие методы класса. Один шаблон, разные объекты.

Атрибуты объекта: статические vs динамические

Атрибуты бывают двух видов. Динамические принадлежат конкретному экземпляру — задаются через 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

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

Инкапсуляция: уровни доступа и @property

Инкапсуляция — ограничение прямого доступа к данным объекта. Цель: защитить состояние от некорректного изменения снаружи.

В 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 реализует инкапсуляцию через протокол дескрипторов: атрибут выглядит как обычный, но доступ к нему проходит через функции.

Наследование: super(), class Child(Parent) и MRO

Схема MRO — порядок разрешения методов при наследовании Python

Наследование позволяет дочернему классу получить атрибуты и методы родительского без дублирования кода.

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 для объектов разных типов.

Абстракция: ABC и @abstractmethod

Абстракция — выделение существенных характеристик объекта и сокрытие деталей реализации. В 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, иначе сам становится абстрактным.

Абстракция фиксирует контракт: «любой персонаж умеет атаковать» — и оставляет конкретную реализацию дочерним классам.

Магические методы: как Python разговаривает с вашим объектом

Магические методы — методы с двойным подчёркиванием в начале и конце имени (от англ. 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__, __str__ и __repr__: создание и представление объекта

__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__, __eq__ и другие: операторы и встроенные функции

Хотите сменить профессию или повысить квалификацию?

Федеральный проект «Активные меры содействия занятости» даёт возможность пройти обучение бесплатно за счёт государства

  • Программы от ведущих вузов России — от 2 месяцев
  • Удостоверение или диплом установленного образца
  • Центр карьеры: 7 500+ вакансий, помощь с трудоустройством
Оставить заявку
image

__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

После освоения четырёх принципов — следующий уровень: классы данных с @dataclass, метаклассы и детали работы MRO. Эти инструменты часто встречаются в реальных Python-проектах и кодовых базах популярных фреймворков. Знание этих концепций отличает начинающего разработчика от специалиста среднего уровня.

@dataclass: меньше кода, тот же результат

Классы, созданные только для хранения данных, часто содержат однообразный __init__. Декоратор @dataclass (Python 3.7+, PEP 557) генерирует его автоматически вместе с __repr__ и __eq__:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

Эти три строки заменяют десять — без ручного написания __init__, __repr__ и __eq__.

Когда использовать: класс хранит данные, логика минимальна или отсутствует. Когда не использовать: нужен нетривиальный __init__ с валидацией или сложная бизнес-логика — тогда лучше явно описать методы.

Метаклассы и type: кто создаёт классы в Python

В 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 и алгоритм C3: предсказуемый порядок поиска методов

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__ в цепочке вызывается ровно один раз, без дублирования и пропусков.

Практика: три задачи для закрепления ООП

Три практические задачи по ООП в Python — карточки заданий

Теория закрепляется только практикой. Вот три задачи с возрастающей сложностью.

Задача 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

Для системного изучения подойдут несколько форматов. Официальная документация Python содержит подробный раздел про классы — docs.python.org/3/tutorial/classes.html. Для практики с автопроверкой — задачи на LeetCode и Codewars, которые удобно решать с ООП-подходом. Структурированные курсы с тренажёрами ускоряют освоение: вы сразу пишете код, получаете обратную связь и движетесь по выстроенной программе.

Студент изучает ООП в Python онлайн за ноутбуком

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

Частые вопросы об объектно-ориентированном программировании в Python

Часто задаваемые вопросы

Чем класс отличается от объекта в Python?

Класс — шаблон (чертёж), объект — конкретный экземпляр по этому шаблону. Например, Book — класс, а book1 = Book("Три товарища", "Ремарк") — объект. У одного класса может быть неограниченное количество объектов с разными данными, но общей структурой. Технически: класс — объект типа type; каждый объект хранит свои данные в __dict__.

Что такое self в Python и зачем он нужен?

self — ссылка на текущий экземпляр класса, первый параметр любого метода экземпляра. Через self метод получает доступ к атрибутам конкретного объекта. Аналог в Java и C++ — this. При вызове book.describe() Python фактически выполняет Book.describe(book), передавая объект первым аргументом.

В чём разница между __str__ и __repr__?

Оба возвращают строку, но для разных целей. __str__ — читаемое представление для print() и конечного пользователя. __repr__ — техническое, для отладки и интерактивной консоли; хорошее __repr__ позволяет воссоздать объект по строке. Если __str__ не определён, Python использует __repr__ как запасной вариант.

Как работает name mangling для приватных атрибутов?

Атрибуты с двойным подчёркиванием (__attr) интерпретатор переименовывает в _ClassName__attr. Это защищает от случайного переопределения имён в подклассах, но не создаёт настоящей приватности — доступ через obj._ClassName__attr технически возможен. В Python нет жёсткого private как в Java.

Когда использовать @dataclass вместо обычного класса?

@dataclass удобен, когда класс нужен только для хранения данных без сложной логики — декоратор автоматически генерирует __init__, __repr__ и __eq__. Если класс содержит нетривиальную валидацию или бизнес-логику, лучше обычный класс с явно описанными методами.

Что такое утиная типизация и как она связана с полиморфизмом?

Утиная типизация (duck typing) — принцип Python: «если объект ведёт себя как утка — он утка». Не важно, от какого класса наследован объект; важно, есть ли у него нужный метод. Это основа полиморфизма в Python: функция вызывает obj.describe() и работает с любым объектом, у которого этот метод есть, — без isinstance().

Можно ли создать по-настоящему приватный атрибут в Python?

Нет. В Python нет механизма настоящего private как в Java. Соглашение _attr означает «внутренний, не трогай». Двойное __attr включает искажение имён, усложняя случайный доступ, но обратиться через _ClassName__attr всё равно можно.

Что такое паттерны проектирования в ООП Python?

Паттерны — типовые решения архитектурных задач: Singleton (один экземпляр класса в программе), Factory (фабрика объектов), Observer (подписка на события). Все реализуются через классы и принципы ООП. Изучать паттерны стоит после уверенного владения наследованием, инкапсуляцией и полиморфизмом.

Где бесплатно практиковаться по теме ООП Python?

Для начинающих подойдут структурированные курсы с тренажёрами и автопроверкой задач. Официальная документация Python содержит подробный раздел про классы (docs.python.org/3/tutorial/classes.html). Задачи на LeetCode и Codewars удобно решать с ООП-подходом. Для системного освоения ИТ-навыков с нуля — в рамках нацпроекта «Кадры» доступны бесплатные программы по ИТ.

Какие книги помогут углубиться в ООП Python?

Для базового уровня: «Изучаем Python» Марка Лутца — подробный разбор классов и ООП. Для среднего: «Чистый Python» Дэна Бейдера — практические паттерны и идиомы. Для продвинутых: «Fluent Python» Лусиано Рамальо — глубокий разбор дескрипторов, метаклассов и магических методов. Все три доступны в русском переводе.

Подайте заявку —
забронируйте место в группе

45 000 мест на 2026 год. Бесплатное обучение по федеральному проекту «Активные меры содействия занятости»

  • Онлайн
  • От 2 месяцев
  • Бесплатно
  • Диплом
Учиться бесплатно
icon