ООП, объектно-ориентированное программирование, это способ писать программу из объектов. Каждый объект хранит свои данные (атрибуты) и умеет что-то делать (методы), а описание, по которому объекты создаются, называется классом. Класс похож на чертёж: по одному чертежу можно собрать сколько угодно объектов, и у каждого будут свои значения.
Ниже разбираем четыре базовых понятия ООП на Python: класс, объект, атрибут, метод. Все примеры мы запустили на Python 3.12 и 3.14, вывод в статье настоящий. Если вы только начинаете учить код, сначала пригодится обзор «Программирование с нуля», а про то, чем на этом языке зарабатывают, рассказано в статье о профессии Python-разработчика.
Что такое ООП простыми словами

Представьте программу про домашних котов. Без ООП данные лежат отдельно, а функции, которые с ними работают, отдельно. Каждый кот это словарь, и любую функцию надо не забыть вызвать с нужным словарём:
cat1 = {"name": "Барсик", "age": 3}
cat2 = {"name": "Мурка", "age": 5}
def cat_say(cat):
return f"{cat['name']}: мяу"
def cat_birthday(cat):
cat["age"] += 1
cat_birthday(cat1)
print(cat_say(cat1), cat1["age"])
Барсик: мяу 4
Пока котов два, всё читается. Когда функций становится полсотни, а словарей с разной структурой десяток, уже непонятно, какая функция к каким данным относится. Опечатка в ключе ("nmae" вместо "name") всплывёт только во время работы программы.
ООП собирает данные и действия в одном месте. Кот становится объектом, у которого есть имя и возраст, а «мяукать» и «стареть на год» он умеет сам. На этом подходе построены Java, C#, C++ и большая часть кода на Python и PHP. В Java и C# классы встречаются с первого урока, хотя короткую программу теперь можно написать и без явного класса: в C# с версии 9, в Java с версии 25.
ООП считают одной из парадигм программирования, то есть одним из общих подходов к тому, как устроить код. Вот чем он отличается от двух других распространённых:
| Подход | Из чего строится программа | Где встречается |
|---|---|---|
| Процедурный | Функции, которые по очереди обрабатывают данные | C, Pascal, короткие скрипты на любом языке |
| Объектно-ориентированный | Объекты: данные плюс действия над ними | Java, C#, C++, Python, PHP |
| Функциональный | Функции без изменяемого состояния, их композиция | Haskell, Elixir, части кода на JavaScript и Python |
Python и JavaScript разрешают смешивать подходы. Скрипт на десять строк можно написать без единого класса, и это нормально.
Класс и объект: в чём разница
Класс описывает, какие данные будут у объектов и что они смогут делать. Объект (его ещё называют экземпляром класса) это конкретная вещь, созданная по этому описанию. Класс Cat один, а котов из него можно сделать тысячу.
Перепишем пример с котами на классы:
class Cat:
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
return f"{self.name}: мяу"
def birthday(self):
self.age += 1
barsik = Cat("Барсик", 3)
murka = Cat("Мурка", 5)
barsik.birthday()
print(barsik.say(), barsik.age)
print(murka.say(), murka.age)
print(type(barsik))
print(isinstance(murka, Cat))
print(barsik is murka)
Барсик: мяу 4
Мурка: мяу 5
<class '__main__.Cat'>
True
False
Что здесь происходит, по строкам:
class Cat:объявляет класс. Имена классов в Python пишут с большой буквы, так советует стандарт оформления кода PEP 8.__init__это конструктор. Python вызывает его сам в моментCat("Барсик", 3)и передаёт туда аргументы.selfобозначает сам объект, который сейчас создаётся или с которым работает метод. Черезself.name = nameимя записывается именно этому коту.barsik = Cat(...)создаёт объект. Скобки после имени класса обязательны.barsik.birthday()вызывает метод. День рождения был у Барсика, и возраст Мурки не изменился.
Последние три строки вывода полезно запомнить. type() показывает, из какого класса сделан объект, isinstance() проверяет принадлежность к классу, а is отвечает, один ли это объект. Два кота из одного класса остаются двумя разными объектами.
Если код хочется сразу запустить у себя, пригодится инструкция как создать и запустить файл Python в VS Code. Официальный учебник по классам лежит в документации Python, раздел 9 «Classes».
Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединитьсяАтрибуты: какие данные хранит объект

Атрибут это переменная, привязанная к объекту или к классу. У кота атрибуты name и age. Обращаются к ним через точку: barsik.name.
Атрибуты бывают двух видов, и путаница между ними даёт самые неприятные ошибки у новичков.
| Вид | Где объявляется | Чей | Пример |
|---|---|---|---|
| Атрибут экземпляра | Внутри __init__ через self |
У каждого объекта свой | self.name = name |
| Атрибут класса | Прямо в теле класса | Общий для всех объектов | species = "кошка" |
class Cat:
species = "кошка" # атрибут класса: общий для всех
def __init__(self, name):
self.name = name # атрибут экземпляра: у каждого свой
barsik = Cat("Барсик")
murka = Cat("Мурка")
print(barsik.species, murka.species)
print(barsik.__dict__)
print(Cat.species)
barsik.species = "кот" # создаёт атрибут у barsik, класс не трогает
print(barsik.species, murka.species, Cat.species)
barsik.color = "рыжий" # атрибут можно добавить на лету
print(barsik.__dict__)
print(hasattr(murka, "color"))
кошка кошка
{'name': 'Барсик'}
кошка
кот кошка кошка
{'name': 'Барсик', 'species': 'кот', 'color': 'рыжий'}
False
Обратите внимание на __dict__: это словарь атрибутов конкретного объекта. Атрибута класса species в нём сначала нет, Python находит его в классе. Когда мы присвоили barsik.species = "кот", у Барсика появилась своя копия, а у Мурки и у класса осталось старое значение.
Если словари вам привычнее объектов, видно, что под капотом объект Python хранит атрибуты почти как словарь. Похожая структура «ключ: значение» лежит в основе формата, который мы разбирали в статье что такое JSON.
Ловушка: список в атрибуте класса
Изменяемые значения (списки, словари) в атрибуте класса делятся между всеми объектами. Корзина одного покупателя внезапно оказывается у другого:
class Cart:
items = [] # ошибка: список общий для всех корзин
def add(self, item):
self.items.append(item)
a = Cart()
b = Cart()
a.add("книга")
print(b.items)
['книга']
Решение: создавать список в __init__, тогда он будет у каждого объекта свой.
class CartFixed:
def __init__(self):
self.items = [] # у каждой корзины свой список
def add(self, item):
self.items.append(item)
a = CartFixed()
b = CartFixed()
a.add("книга")
print(a.items, b.items)
['книга'] []
Методы: что умеет объект
Метод это функция, объявленная внутри класса. Первым параметром обычный метод получает self, поэтому видит атрибуты своего объекта и может их менять. В Python есть ещё два вида методов, и их легко перепутать.
class Temperature:
scale = "°C"
def __init__(self, celsius):
self.celsius = celsius
def to_fahrenheit(self): # обычный метод: работает с объектом
return self.celsius * 9 / 5 + 32
@classmethod
def from_fahrenheit(cls, fahrenheit): # метод класса: создаёт объект
return cls((fahrenheit - 32) * 5 / 9)
@staticmethod
def is_freezing(celsius): # статический: просто функция в классе
return celsius <= 0
t = Temperature(25)
print(t.to_fahrenheit())
print(Temperature.from_fahrenheit(212).celsius)
print(Temperature.is_freezing(-3))
print(Temperature.to_fahrenheit(t))
77.0
100.0
True
77.0| Вид метода | Первый параметр | Видит | Когда нужен |
|---|---|---|---|
| Обычный | self (объект) |
Атрибуты объекта и класса | Почти всегда: действия конкретного объекта |
@classmethod |
cls (класс) |
Только атрибуты класса | Другой способ создать объект, например из градусов Фаренгейта |
@staticmethod |
Нет | Ничего, кроме аргументов | Вспомогательная функция, которая по смыслу относится к классу |
Внутри методов работают обычные арифметические знаки: *, /, +, сравнение <=. Если какие-то из них непривычны, загляните в разбор операторов Python.
Последняя строка примера показывает, как метод устроен изнутри. Запись t.to_fahrenheit() Python превращает в Temperature.to_fahrenheit(t): объект подставляется в self автоматически. Описание обоих декораторов есть в справочнике встроенных функций, разделы classmethod и staticmethod.
Отдельно стоят «магические» методы с двумя подчёркиваниями: __init__, __str__, __len__. Их вызывает сам Python в определённых ситуациях. Например, __str__ срабатывает, когда объект передают в print(), это видно в следующем примере.
Как понятия работают вместе: банковский счёт
Соберём всё в один небольшой класс. Счёт хранит владельца, баланс и историю операций, умеет пополняться и списывать деньги, а снять больше, чем лежит на счёте, не даёт.
class Account:
bank = "Учебный банк"
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
self.history = []
def deposit(self, amount):
if amount <= 0:
raise ValueError("Сумма должна быть больше нуля")
self.balance += amount
self.history.append(f"+{amount}")
def withdraw(self, amount):
if amount > self.balance:
raise ValueError("Недостаточно средств")
self.balance -= amount
self.history.append(f"-{amount}")
def __str__(self):
return f"{self.owner}: {self.balance} ₽"
anna = Account("Анна", 1000)
anna.deposit(500)
anna.withdraw(300)
print(anna)
print(anna.history)
try:
anna.withdraw(5000)
except ValueError as error:
print("Ошибка:", error)
Анна: 1200 ₽
['+500', '-300']
Ошибка: Недостаточно средств
Разложим пример по понятиям из статьи:
- класс:
Account, описание любого счёта; - объект:
anna, конкретный счёт Анны; - атрибуты:
owner,balanceиhistoryу каждого счёта свои,bankобщий для всех; - методы:
depositиwithdrawменяют баланс по правилам,__str__отвечает за красивый вывод.
Главная выгода видна в withdraw. Проверка «хватает ли денег» живёт в одном месте. Без класса её пришлось бы повторять перед каждым списанием по всей программе, и однажды кто-нибудь бы её забыл.
Такие задачи (счёт, библиотека, склад) хорошо подходят для тренировки: в каждой понятно, какие данные хранить и какие правила проверять. Больше упражнений с проверкой кода дают программы из каталога курсов по ООП.
Четыре принципа ООП коротко
Класс, объект, атрибут и метод это словарь ООП. Поверх него стоят четыре принципа, которые объясняют, зачем всё это нужно. Подробный разбор каждого тянет на отдельную статью, здесь даём определение и связь с примерами выше.
| Принцип | Суть в одной фразе | Где в наших примерах |
|---|---|---|
| Инкапсуляция | Данные и правила работы с ними лежат в одном объекте | Баланс меняется только через deposit и withdraw |
| Наследование | Новый класс получает атрибуты и методы родителя | Rectangle и Circle ниже наследуют Shape |
| Полиморфизм | Один и тот же вызов работает с объектами разных классов | shape.area() для любой фигуры |
| Абстракция | В классе остаётся только то, что важно для задачи | У счёта нет цвета карты и пин-кода, они здесь не нужны |
class Shape:
def area(self):
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return round(3.14159 * self.radius ** 2, 2)
for shape in [Rectangle(3, 4), Circle(2)]:
print(type(shape).__name__, shape.area())
Rectangle 12
Circle 12.57
Цикл не знает, какая фигура перед ним, и просто зовёт area(). Добавите класс Triangle со своим методом area, и цикл менять не придётся. Как проектировать такие классы, чтобы они не разрастались, объясняют принципы SOLID, о них в статье KISS, DRY, YAGNI и SOLID.
Плюсы и минусы ООП
У подхода есть цена, и её полезно понимать заранее, чтобы не заворачивать в классы всё подряд.
| Плюсы | Минусы |
|---|---|
| Данные и правила их изменения лежат рядом, ошибку проще найти | Для скрипта на 20 строк классы добавляют лишний код |
| Готовый класс можно переиспользовать и расширить через наследование | Глубокая цепочка наследования быстро становится запутанной |
| Команда делит работу по классам и меньше мешает друг другу | Нужно время на проектирование: какие будут классы и как они связаны |
| Код ближе к предметной области: счёт, заказ, пользователь | Новичку сложнее начать: сразу появляются self, конструктор, магические методы |
Практическое правило такое. Если в программе несколько сущностей, у каждой свои данные и свои правила, классы окупаются. Если вы просто читаете файл и считаете сумму, хватит функций, например встроенной sum() для списка.
Совет: не начинайте учёбу с наследования. Сначала научитесь писать один класс с конструктором и двумя-тремя методами, как Account выше. Наследование понадобится, когда появятся два похожих класса и захочется убрать повторяющийся код.
ООП в других языках
Понятия одни и те же, меняется синтаксис. Тот же кот на JavaScript и C++ (оба примера мы скомпилировали и запустили, вывод Барсик: мяу):
class Cat {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
return `${this.name}: мяу`;
}
}
const barsik = new Cat("Барсик", 3);
console.log(barsik.say());
#include <iostream>
#include <string>
class Cat {
public:
std::string name;
int age;
Cat(std::string n, int a) : name(n), age(a) {}
std::string say() { return name + ": мяу"; }
};
int main() {
Cat barsik("Барсик", 3);
std::cout << barsik.say() << std::endl;
}| Язык | Конструктор | Ссылка на объект | Создание объекта | Документация |
|---|---|---|---|---|
| Python | __init__ |
self (явно в параметрах) |
Cat("Барсик") |
docs.python.org |
| JavaScript | constructor |
this |
new Cat("Барсик") |
MDN |
| Java | Метод с именем класса | this |
new Cat("Барсик") |
Oracle |
| C# | Метод с именем класса | this |
new Cat("Барсик") |
Microsoft Learn |
| C++ | Метод с именем класса | this (указатель) |
Cat barsik("Барсик") |
cppreference |
Главное отличие Python от остальных: self пишется в параметрах каждого метода явно, а в Java, C# и C++ this доступен сам. Ещё в Java и C# методы живут только внутри классов, даже точка входа main: короткая программа без явного класса (в C# 9 и Java 25) лишь прячет его за кадром, компилятор создаёт класс сам. Поэтому ООП там встречается с первого урока. Если вы выбираете между языками, сравните дорожные карты Java-разработчика и C++ разработчика.
Частые ошибки новичков

Вот пять ошибок, которые чаще всего встречаются в первых классах. Тексты ошибок ниже скопированы из вывода Python 3.12 и 3.14, они совпадают.
Забыли self в методе
class Cat:
def say():
return "мяу"
Cat().say()
TypeError: Cat.say() takes 0 positional arguments but 1 was given
Python передал в метод объект, а принять его некуда. Решение: def say(self):.
Опечатка в __init__
class Cat:
def __int__(self, name):
self.name = name
Cat("Барсик")
TypeError: Cat() takes no arguments
Вместо __init__ написано __int__, это другой магический метод (перевод в целое число). Конструктора у класса нет, поэтому аргументы ему не нужны. Сообщение сбивает с толку, так что при ошибке «takes no arguments» первым делом проверяйте написание __init__.
Значение ушло в локальную переменную
class Cat:
def __init__(self, name):
name = name
print(Cat("Барсик").name)
AttributeError: 'Cat' object has no attribute 'name'
Строка name = name записала значение в локальную переменную, которая исчезла после выхода из конструктора. Решение: self.name = name.
Вызвали метод без скобок
print(barsik.say)
<bound method Cat.say of <__main__.Cat object at 0x105b5dfd0>>
Ошибки нет, и это хуже: программа работает, но печатает описание метода вместо «мяу». Адрес в конце у вас будет другим. Нужно barsik.say().
Метод вызвали прямо у класса
Cat.say()
TypeError: Cat.say() missing 1 required positional argument: 'self'
У класса нет конкретного кота, которого можно подставить в self. Сначала создайте объект: Cat("Барсик").say(). Если методу объект не нужен, сделайте его @staticmethod.
Что учить после основ
Когда класс с конструктором и парой методов пишется без подсказок, дальше обычно идут три темы.
- Наследование и
super(): как дочерний класс вызывает конструктор родителя. - Свойства
@property: атрибут, при чтении которого выполняется проверка или вычисление. - Датаклассы: модуль
dataclassesсам пишет__init__, сравнение и вывод для классов, которые в основном хранят данные.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(2, 3)
print(p)
print(p == Point(2, 3))
Point(x=2, y=3)
True
Три строки описания заменили конструктор и метод сравнения. Подробности в документации модуля dataclasses. Дальше ООП встречается в любом фреймворке: модели в Django это классы, и про работу на этом стеке есть статья кто такой Django-разработчик.
Где освоить ООП системно
Понять, чем класс отличается от объекта, можно за вечер. Научиться раскладывать настоящую задачу на классы так, чтобы через полгода код не пришлось переписывать, получается только на практике с проверкой. На курсах ООП обычно идёт отдельным модулем внутри программы по языку, с проектом и проверкой кода наставником.
Программы, где ООП изучают отдельным навыком, есть в каталоге курсов по ООП. Если хотите выбрать язык и учить ООП на нём, смотрите подборку курсов Python. Ниже пять программ с навыком ООП, их можно сравнить по цене и длительности.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| PHP-разработчик с нуля до PRO Перейти на сайт курса | 114 876 ₽ | 4176 ₽/мес. | 7 месяцев | Обзор курса | |
| Frontend-разработчик с нуля Перейти на сайт курса | 113 400 ₽ | 5385 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 161 200 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| PHP-разработчик. Базовый уровень Перейти на сайт курса | 81 107 ₽ | 5227 ₽/мес. | 3 месяца | Обзор курса |
Больше программ — в полном каталоге курсов по оОП




