ООП в Python для начинающих: классы, наследование и магические методы с примерами
Если вы только осваиваете ООП в Python для начинающих, важно понять ключевые идеи: класс описывает «тип» объекта, а объект — это конкретный экземпляр этого типа. Ниже — практическое руководство с примерами, которое поможет уверенно применять ООП в реальных задачах.
Что такое класс и объект в Python
Класс — это шаблон, который описывает данные (атрибуты) и поведение (методы). Объект — это созданный по классу экземпляр с конкретными значениями.
import math
class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def length(self) -> float:
return math.hypot(self.x, self.y)
p = Point(3, 4)
print(p.length()) # 5.0
Метод __init__ инициализирует состояние объекта. Аргумент self — это ссылка на текущий экземпляр.
Атрибуты класса и экземпляра: в чем разница
Атрибуты класса общие для всех экземпляров, атрибуты экземпляра принадлежат конкретному объекту.
class Counter:
created = 0 # атрибут класса
def __init__(self):
Counter.created += 1 # меняем через имя класса
self.value = 0 # атрибут экземпляра
a = Counter()
b = Counter()
print(Counter.created) # 2
print(a.value, b.value) # 0 0
Совет: изменяйте атрибуты класса через имя класса, чтобы не затереть их одноименным атрибутом у экземпляра.
Инкапсуляция и property: безопасный доступ к данным
В Python нет «жесткой» приватности, используются соглашения: _attr — внутренний атрибут, __attr — «сильная» защита от случайного доступа через name mangling. Для контроля чтения/записи используйте @property.
class BankAccount:
def __init__(self, owner: str, balance: float = 0.0):
self.owner = owner
self._balance = balance
@property
def balance(self) -> float:
return self._balance
@balance.setter
def balance(self, amount: float):
if amount < 0:
raise ValueError("Баланс не может быть отрицательным")
self._balance = amount
acc = BankAccount("Иван", 1000)
acc.balance = 1500
print(acc.balance)
@property помогает инкапсулировать логику валидации без изменения внешнего интерфейса класса.
Наследование и полиморфизм: общий интерфейс — разная реализация
Наследование позволяет разделять код и расширять поведение. Полиморфизм дает возможность вызывать одинаковые методы у объектов разных классов.
class Shape:
def area(self) -> float:
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, w: float, h: float):
self.w = w
self.h = h
def area(self) -> float:
return self.w * self.h
class Circle(Shape):
def __init__(self, r: float):
self.r = r
def area(self) -> float:
from math import pi
return pi * self.r ** 2
shapes = [Rectangle(2, 3), Circle(1)]
print([round(s.area(), 2) for s in shapes]) # [6, 3.14]
В базовом классе оставляем «контракт» через NotImplementedError, а в наследниках реализуем метод.
Магические методы: как «учить» объекты вести себя как встроенные
Магические методы (dunder) позволяют интегрировать объекты с синтаксисом Python: печать, сравнение, сортировка и т. д.
class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __str__(self):
return f"({self.x}, {self.y})"
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __lt__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) < (other.x, other.y)
points = [Point(1,3), Point(0,5)]
print(points) # использует __repr__
print(Point(1,3) == Point(1,3)) # True
print(sorted(points)) # использует __lt__
Рекомендация: в сравнении возвращайте NotImplemented для неподдерживаемых типов — это позволит Python попробовать обратное сравнение и избежать странных ошибок.
Классовые и статические методы
@classmethod получает класс (cls) и удобен для альтернативных конструкторов. @staticmethod — просто функция внутри класса, не связана ни с классом, ни с экземпляром.
class User:
def __init__(self, username: str):
self.username = username
@classmethod
def from_email(cls, email: str):
name = email.split("@", 1)[0]
return cls(name)
@staticmethod
def is_valid_username(name: str) -> bool:
return name.isalnum() and 3 <= len(name) <= 20
u = User.from_email("alex99@example.com")
print(u.username) # alex99
print(User.is_valid_username(u.username))
Мини‑проект: корзина заказа за 20 строк кода
Склеим все вместе: инкапсуляцию, __repr__, @property и простую бизнес‑логику.
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
def __repr__(self):
return f"{self.name}({self.price:.2f})"
class Cart:
def __init__(self):
self._items: list[tuple[Product, int]] = []
def add(self, product: Product, qty: int = 1):
if qty <= 0:
raise ValueError("Количество должно быть положительным")
self._items.append((product, qty))
@property
def total(self) -> float:
return sum(p.price * qty for p, qty in self._items)
def __str__(self):
lines = [f"- {p.name} x{qty} = {p.price*qty:.2f}" for p, qty in self._items]
lines.append(f"Итого: {self.total:.2f}")
return "\n".join(lines)
cart = Cart()
cart.add(Product("Книга", 500), 2)
cart.add(Product("Чехол", 250), 1)
print(cart)
Такой код легко расширять: добавить скидки, налоги, сериализацию — всё это естественно ложится на ООП.
Частые ошибки и лучшие практики
- Не используйте изменяемые значения по умолчанию в __init__ (например, list, dict). Лучше задавайте None и создавайте внутри.
- Отделяйте атрибуты класса и экземпляра. Для счетчиков и констант — атрибуты класса, для состояния — атрибуты экземпляра.
- Определяйте __repr__ для удобной отладки — это экономит часы времени.
- В магических методах проверяйте тип через isinstance и возвращайте NotImplemented, если тип не поддерживается.
- Не усложняйте иерархии. Если нужен общий интерфейс — начинайте с небольшого базового класса и расширяйте по мере необходимости.
Что дальше изучать
После основ ООП стоит посмотреть на dataclass, абстрактные базовые классы (abc), протоколы и композицию. Хотите быстрее пройти путь от теории к практике? Попробуйте интенсивный курс — Прокачать ООП на практике на курсе «Python с Нуля до Гуру» — с домашками, разбором ошибок и менторством.
Теперь у вас есть базовый набор инструментов: классы, наследование, property, магические методы и фабрики через @classmethod. Этого достаточно, чтобы проектировать чистые и расширяемые решения на Python и уверенно двигаться дальше.
-
Создано 02.01.2026 17:01:31
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.