Django-Admin.ru

Классы и ООП в Python

Классы в Python с нуля: class, __init__ и self, атрибуты, метод класса @classmethod и @staticmethod, @property, наследование и super(), dataclass.

Редакция Django-Admin.ru
· обновлено · 9 мин чтения

Содержание · 12 разделов
  1. Как создать класс и объект
  2. Атрибуты экземпляра и атрибуты класса
  3. Метод класса в Python: обычный, @classmethod и @staticmethod
  4. Специальные методы: __str__, __repr__, __eq__
  5. Свойства: @property
  6. Наследование и super()
  7. Инкапсуляция: _ и __
  8. dataclass: класс для хранения данных
  9. Четыре принципа ООП коротко
  10. Частые ошибки
  11. Забыли self в объявлении метода
  12. Забыли self. при обращении к атрибуту
  13. Опечатка в __init__
  14. Вызов метода у класса, а не у объекта
  15. Изменяемый атрибут класса
  16. Частые вопросы
  17. Чем класс отличается от объекта?
  18. Нужно ли изучать ООП новичку?
  19. Обязательно ли называть первый параметр self?
  20. Как посмотреть все атрибуты объекта?
  21. Можно ли объявить в классе несколько конструкторов?
  22. Итог

Класс в Python — это шаблон, по которому создаются объекты: он описывает, какие данные хранит объект (атрибуты) и что умеет делать (методы). Класс объявляется словом class, начальное состояние задаёт метод __init__, а self внутри методов — ссылка на конкретный объект. Короткий ответ:

class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        return f"{self.name}: Гав!"

rex = Dog("Рекс")
print(rex.bark())  # → Рекс: Гав!

Ниже — объектно-ориентированное программирование на Python с нуля: создание класса и объектов, атрибуты экземпляра и класса, обычный метод, метод класса и статический метод, свойства, наследование, dataclass и ошибки, которые чаще всего делают новички. Примеры проверены на Python 3.12.

Как создать класс и объект

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Недостаточно средств")
        self.balance -= amount

acc = BankAccount("Анна")
acc.deposit(1000)
acc.withdraw(300)
print(acc.owner, acc.balance)    # → Анна 700

other = BankAccount("Олег", 50)
print(other.balance)             # → 50
print(isinstance(acc, BankAccount))  # → True
  • class BankAccount: объявляет класс. Имена классов по соглашению пишут в CamelCase.
  • BankAccount("Анна") создаёт экземпляр (объект). Python вызывает __init__ и передаёт в него новый объект как self, а "Анна" — как owner.
  • self.owner = owner создаёт атрибут экземпляра — у каждого объекта он свой: у acc и other разные балансы.
  • Методы — это функции, объявленные внутри класса. Первым параметром они получают self.

Вызов acc.deposit(500) Python превращает в BankAccount.deposit(acc, 500) — поэтому self не нужно передавать вручную.

Атрибуты экземпляра и атрибуты класса

Переменные, объявленные прямо в теле класса, — атрибуты класса. Они общие для всех экземпляров:

class User:
    platform = "Django-Admin.ru"   # атрибут класса
    count = 0

    def __init__(self, name):
        self.name = name           # атрибут экземпляра
        User.count += 1

a = User("Анна")
b = User("Олег")
print(a.platform, b.platform)  # → Django-Admin.ru Django-Admin.ru
print(User.count)              # → 2

a.platform = "другой сайт"
print(a.platform, b.platform, User.platform)
# → другой сайт Django-Admin.ru Django-Admin.ru

Присваивание a.platform = ... не меняет атрибут класса, а создаёт у a собственный атрибут с тем же именем. Атрибуты класса подходят для констант и счётчиков, а изменяемые данные конкретного объекта храните в self.

Метод класса в Python: обычный, @classmethod и @staticmethod

В классе бывают три вида методов. Они различаются тем, что получают первым аргументом:

from datetime import date

class Person:
    adult_age = 18

    def __init__(self, name, birth_year):
        self.name = name
        self.birth_year = birth_year

    def age(self, year):                 # обычный метод: получает self
        return year - self.birth_year

    @classmethod
    def from_string(cls, text):          # метод класса: получает cls
        name, year = text.split(";")
        return cls(name, int(year))

    @staticmethod
    def is_valid_year(year):             # статический: ничего не получает
        return 1900 <= year <= date.today().year

p = Person.from_string("Анна;1995")
print(p.name, p.age(2026))          # → Анна 31
print(Person.is_valid_year(1850))   # → False
Вид методаОбъявлениеПервый аргументДля чего
Обычныйdef m(self)экземпляр selfработа с данными конкретного объекта
Метод класса@classmethodсам класс clsальтернативные конструкторы, работа с атрибутами класса
Статический@staticmethodнетвспомогательная функция, логически связанная с классом

Метод класса особенно полезен при наследовании: cls — это тот класс, через который метод вызвали. Если объявить class Student(Person): pass, то Student.from_string("Иван;2004") вернёт объект Student, а не Person. Похожий приём вы встретите в стандартной библиотеке: dict.fromkeys() и datetime.fromisoformat() — тоже методы класса.

@classmethod и @staticmethod — встроенные декораторы; как устроены декораторы вообще, разобрано в статье о декораторах в Python.

Специальные методы: __str__, __repr__, __eq__

Методы с двойными подчёркиваниями (их называют dunder-методами или «магическими») подключают объект к встроенным механизмам языка: print(), сравнению, арифметике. Без них объект выводится малоинформативно, а два объекта с одинаковыми данными не равны:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

print(Point(1, 2))                 # → <__main__.Point object at 0x...>
print(Point(1, 2) == Point(1, 2))  # → False

Добавим нужные методы:

class Money:
    def __init__(self, amount, currency="RUB"):
        self.amount = amount
        self.currency = currency

    def __str__(self):                  # для print() и str()
        return f"{self.amount} {self.currency}"

    def __repr__(self):                 # для отладки и вывода в коллекциях
        return f"Money({self.amount!r}, {self.currency!r})"

    def __eq__(self, other):            # оператор ==
        return (self.amount, self.currency) == (other.amount, other.currency)

    def __add__(self, other):           # оператор +
        return Money(self.amount + other.amount, self.currency)

m = Money(100) + Money(250)
print(m)                     # → 350 RUB
print([m])                   # → [Money(350, 'RUB')]
print(Money(5) == Money(5))  # → True

Полный список таких методов — в разделе Data model документации.

Свойства: @property

@property позволяет обращаться к методу как к атрибуту и проверять значения при присваивании:

class Product:
    def __init__(self, title, price):
        self.title = title
        self.price = price          # сработает сеттер

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("Цена не может быть отрицательной")
        self._price = value

    @property
    def price_with_vat(self):
        return round(self.price * 1.2, 2)

p = Product("Книга", 500)
print(p.price, p.price_with_vat)  # → 500 600.0
p.price = 650
print(p.price_with_vat)           # → 780.0
p.price = -1                      # → ValueError: Цена не может быть отрицательной

Снаружи price выглядит как обычный атрибут, но любое присваивание проходит через проверку.

Наследование и super()

Дочерний класс получает все атрибуты и методы родителя и может переопределить любые из них:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

    def introduce(self):
        return f"{self.name} говорит: {self.speak()}"

class Cat(Animal):
    def speak(self):
        return "Мяу"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)      # вызвать __init__ родителя
        self.breed = breed

    def speak(self):
        return "Гав"

for pet in [Cat("Мурка"), Dog("Рекс", "овчарка")]:
    print(pet.introduce())
# → Мурка говорит: Мяу
# → Рекс говорит: Гав

print(issubclass(Cat, Animal))  # → True

super().__init__(name) вызывает конструктор родителя, чтобы не дублировать его код. Метод introduce() объявлен один раз в Animal, но вызывает speak() того класса, к которому относится объект, — это и есть полиморфизм.

Инкапсуляция: _ и __

В Python нет модификаторов private и public, вместо них действуют соглашения:

  • self._balance — одно подчёркивание: «внутренний атрибут, не трогайте снаружи». Доступ технически открыт.
  • self.__pin — два подчёркивания: Python переименовывает атрибут в _Account__pin (name mangling). Обращение acc.__pin снаружи вызовет AttributeError, но acc._Account__pin по-прежнему сработает. Механизм защищает от случайных конфликтов имён при наследовании, но доступ снаружи не закрывает.

dataclass: класс для хранения данных

Если класс в основном хранит данные, декоратор @dataclass сгенерирует __init__, __repr__ и __eq__ по аннотациям полей:

from dataclasses import dataclass, field

@dataclass
class Order:
    id: int
    customer: str
    items: list[str] = field(default_factory=list)
    paid: bool = False

o = Order(1, "Анна")
o.items.append("кофе")
print(o)                                  # → Order(id=1, customer='Анна', items=['кофе'], paid=False)
print(o == Order(1, "Анна", ["кофе"]))    # → True

Для данных с валидацией (например, из JSON или API) используют Pydantic, а модели Django ORM — тоже классы, наследники models.Model.

Четыре принципа ООП коротко

ПринципЧто означаетКак выглядит в Python
Инкапсуляцияданные и методы для работы с ними собраны в объектеатрибуты в self, соглашение _name, @property
Наследованиеновый класс расширяет существующийclass Dog(Animal):, super()
Полиморфизмодин интерфейс — разное поведениеу Cat и Dog свой speak()
Абстракциянаружу виден только нужный интерфейспубличные методы, модуль abc

Частые ошибки

Забыли self в объявлении метода

class Counter:
    def increment():
        pass

Counter().increment()
# → TypeError: Counter.increment() takes 0 positional arguments but 1 was given

Python всегда передаёт объект первым аргументом, поэтому у обычного метода должен быть параметр self.

Забыли self. при обращении к атрибуту

Внутри метода value += 1 вместо self.value += 1 даёт UnboundLocalError: cannot access local variable 'value' where it is not associated with a value: без self. Python ищет локальную переменную.

Опечатка в __init__

Если написать def __int__(self, name):, конструктор не будет вызван, а Dog("Рекс") упадёт с TypeError: Dog() takes no arguments. Проверьте имя: __init__, по два подчёркивания с каждой стороны.

Вызов метода у класса, а не у объекта

Dog.bark() даёт TypeError: Dog.bark() missing 1 required positional argument: 'self'. Сначала создайте объект: Dog("Рекс").bark().

Изменяемый атрибут класса

class Team:
    members = []          # один список на все команды!

    def __init__(self, name):
        self.name = name

    def add(self, person):
        self.members.append(person)

a = Team("A")
b = Team("B")
a.add("Анна")
print(b.members)  # → ['Анна']

Списки и словари, которые должны быть у каждого объекта свои, создавайте в __init__: self.members = [].

Частые вопросы

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

Класс — описание (чертёж), объект — конкретный экземпляр, созданный по нему. Dog — класс, rex = Dog("Рекс") — объект. Из одного класса можно создать сколько угодно объектов со своими данными.

Нужно ли изучать ООП новичку?

Да, но после основ: переменных, условий, циклов и функций. Классы в Python встречаются повсюду — в Django, FastAPI, pandas, — и читать такой код без понимания ООП трудно. Если вы искали материалы школьного курса информатики (8 или 10 класс), «класс» там означает год обучения: начните с руководства Python для начинающих.

Обязательно ли называть первый параметр self?

Нет, имя self — соглашение: Python передаст объект в первый параметр с любым именем, и def __init__(this, x) тоже сработает. Но редакторы, линтеры и другие разработчики ожидают self в обычных методах и cls в методах класса, поэтому другие имена только запутают.

Как посмотреть все атрибуты объекта?

vars(obj) (то же самое, что obj.__dict__) возвращает словарь атрибутов экземпляра:

acc = BankAccount("Анна", 700)
print(vars(acc))  # → {'owner': 'Анна', 'balance': 700}

dir(obj) показывает больше: ещё методы и атрибуты класса, включая служебные с двойным подчёркиванием.

Можно ли объявить в классе несколько конструкторов?

Перегрузки методов по числу аргументов в Python нет: если объявить __init__ дважды, останется последний. Необязательные параметры получают значения по умолчанию, а другие способы создать объект оформляют методами класса — как Person.from_string() в примере выше.

Итог

Класс объединяет данные и поведение: __init__ задаёт начальное состояние, self указывает на текущий объект, @classmethod получает класс и подходит для альтернативных конструкторов, @staticmethod — для вспомогательных функций. Наследование с super() позволяет расширять готовые классы, а @dataclass убирает шаблонный код для классов-контейнеров. Остальной синтаксис — в шпаргалке по синтаксису Python; закрепить ООП помогут задачи с решениями.

  • #Python
  • #Классы
  • #ООП
  • #classmethod
  • #Синтаксис
↑ ↓ выборEnter открытьEsc закрыть