Классы и ООП в Python
Классы в Python с нуля: class, __init__ и self, атрибуты, метод класса @classmethod и @staticmethod, @property, наследование и super(), dataclass.
Содержание · 12 разделов
- Как создать класс и объект
- Атрибуты экземпляра и атрибуты класса
- Метод класса в Python: обычный, @classmethod и @staticmethod
- Специальные методы: __str__, __repr__, __eq__
- Свойства: @property
- Наследование и super()
- Инкапсуляция: _ и __
- dataclass: класс для хранения данных
- Четыре принципа ООП коротко
- Частые ошибки
- Забыли self в объявлении метода
- Забыли self. при обращении к атрибуту
- Опечатка в __init__
- Вызов метода у класса, а не у объекта
- Изменяемый атрибут класса
- Частые вопросы
- Чем класс отличается от объекта?
- Нужно ли изучать ООП новичку?
- Обязательно ли называть первый параметр self?
- Как посмотреть все атрибуты объекта?
- Можно ли объявить в классе несколько конструкторов?
- Итог
Класс в 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
- #Синтаксис