$ sudo teach IT
Модуль 9 · Что дальше: ООП и асинхронность · Урок 9.1

Классы и объекты

Первое знакомство с объектно-ориентированным программированием: что такое класс, объект и зачем объединять данные с функциями

Теория~30 минутНовичокclass__init__selfАтрибутыМетодыЭкземпляры

Зачем объединять данные и функции вместе

До сих пор вы держали данные и код, который с ними работает, отдельно друг от друга. Например, для книги вы бы завели три переменные — title, author, pages — а печатать описание книги пришлось бы отдельной функцией, в которую эти три переменные каждый раз передаются заново. Если книг несколько, переменных становится в три раза больше, и легко перепутать, какой автор к какому названию относится.

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

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

Аналогия: книга в библиотеке

Атрибуты (что она знает о себе)

  • название
  • автор
  • количество страниц

Методы (что с ней можно сделать)

  • показать короткое описание
  • отметить как прочитанную

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

Класс — чертёж, объект — изделие по чертежу

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

Класс описывается ключевым словом class: имя класса, двоеточие и тело с отступом — так же, как вы описывали функцию через def.

class Book:
    pass  # пока пустой класс
Часть кодаЧто делает
classключевое слово, с которого начинается описание класса
Bookимя класса; по договорённости пишется с большой буквы, каждое новое слово тоже с большой — это называется CamelCase: Book, BankAccount
:как и у def или if, двоеточие открывает тело класса с отступом
passзаглушка: тело не должно быть пустым, а функционала пока нет

Этот класс уже работает, но пока бесполезен — в нём нет ни данных, ни действий. Наполним его по шагам.

Соглашение об именах: классы называют в CamelCase (Car, UserProfile), а функции и переменные — в привычном вам snake_case (user_profile). Это не требование языка, а негласная договорённость всех Python-разработчиков — нарушите её, и код будет работать, но сбивать с толку других.

Конструктор __init__: как задать объекту начальные данные

Когда вы регистрируетесь в сервисе, вам сразу дают анкету: имя, email, пароль. Пока вы её не заполнили, аккаунта фактически нет — только пустая форма. У объектов то же самое: сразу после создания нужно заполнить его данными. Для этого в классе описывают специальную функцию, которая выполняется автоматически в момент создания объекта.

Такая функция называется конструктором, а в Python она всегда носит имя __init__ (от английского initialize — «инициализировать», то есть задать начальное состояние). Двойные подчёркивания с двух сторон — не опечатка: так в Python помечают служебные («магические», или dunder, от double underscore) методы — методы со особым, предопределённым самим языком поведением. __init__ — самый частый из них.

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
СтрокаЧто происходит
def __init__(self, title, author, pages):объявление конструктора; параметры title, author, pages — обычные параметры функции, ничем не отличаются от параметров def, который вы уже знаете
self.title = titleзначение параметра title сохраняется в объекте под тем же именем — о self подробно дальше
self.author = author и следующаято же самое для остальных данных — каждую строку конструктора можно читать так: «сохрани переданное значение внутри объекта»

Пока класс сам по себе не выполняет этот код — __init__ сработает только тогда, когда вы создадите объект. Как именно — разберём в следующем разделе.

self — ссылка на «этот конкретный объект»

Представьте, что вы заполняете анкету не для себя, а сразу за всех сотрудников компании — сто одинаковых бланков. Чтобы не запутаться, в какой бланк что писать, вам нужно постоянно указывать: «вот в этот, текущий». В Python эту роль играет self: он показывает, с каким именно объектом сейчас работает код.

self — первый параметр каждого метода класса, и обозначает он тот объект, у которого этот метод вызвали. Слово self — тоже просто соглашение (можно назвать параметр иначе, язык не запретит), но менять его не стоит: все, кто читает ваш код, ожидают увидеть именно self.

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

    def description(self):
        return f"'{self.title}' by {self.author}"

book = Book("1984", "George Orwell")
print(book.description())
СтрокаЧто происходит
def description(self):метод без дополнительных параметров — но self есть всё равно, он есть всегда
return f"'{self.title}' by {self.author}"внутри f-строки self.title и self.author берут данные именно из того объекта, для которого метод вызван
book = Book("1984", "George Orwell")создаём объект — подробно в следующем разделе
book.description()вызываем метод через точку; выведет '1984' by George Orwell

Когда вы пишете book.description(), Python сам подставляет book как первый аргумент — то есть фактически выполняется Book.description(book). Именно поэтому self не передают руками при вызове: интерпретатор делает это за вас.

Запомните: self пишут в определении метода (в скобках после имени метода), но не пишут при вызове метода — там уже стоит имя объекта перед точкой.

Создание объекта и обращение к его данным

Класс — это только описание того, какими будут будущие объекты. Чтобы получить настоящий, «рабочий» объект, класс нужно вызвать как функцию — это называется созданием экземпляра.

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

book = Book("1984", "George Orwell")

print(book.title)   # 1984
print(book.author)  # George Orwell
СтрокаЧто происходит
Book("1984", "George Orwell")Python создаёт новый пустой объект типа Book и сразу вызывает для него __init__, передавая туда "1984" и "George Orwell" — их получают параметры title и author (параметр self Python подставляет сам, вручную его не передают)
book = ...готовый объект сохраняется в переменную book, дальше с ним можно работать как с обычным значением
book.titleобращение к атрибуту объекта через точку — вернёт "1984", то самое значение, которое в конструкторе сохранили как self.title

Несколько объектов одного класса — у каждого свои данные

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

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

book1 = Book("1984", 328)
book2 = Book("Война и мир", 1225)

print(book1.title, book1.pages)   # 1984 328
print(book2.title, book2.pages)   # Война и мир 1225

book1.pages = 330               # меняем данные только у book1
print(book1.pages)                # 330
print(book2.pages)                # 1225 -- не изменилось
СтрокаЧто происходит
book1 = Book(...) и book2 = Book(...)два независимых объекта, созданных по одному классу
book1.pages = 330напрямую меняем атрибут конкретного объекта, как обычную переменную, только через точку
book2.pages всё ещё 1225изменение book1 никак не задело book2 — у каждого объекта своя, отдельная копия атрибутов

Атрибут класса: данные, общие для всех объектов

У всех книг, независимо от названия и автора, один и тот же материал — бумага. Незачем хранить это отдельно в каждом объекте: такую общую характеристику удобнее задать один раз на весь класс, а не повторять в конструкторе для каждого экземпляра.

Такие данные называют атрибутом класса — в отличие от атрибута экземпляра (который вы уже видели, он задаётся через self в __init__), атрибут класса пишут прямо в теле класса, без self.

class Book:
    material = "бумага"          # атрибут КЛАССА -- один на всех

    def __init__(self, title):
        self.title = title        # атрибут ЭКЗЕМПЛЯРА -- свой у каждого

book1 = Book("1984")
book2 = Book("Дюна")

print(book1.material)   # бумага
print(book2.material)   # бумага -- общее значение
print(Book.material)    # бумага -- можно взять и у самого класса
СтрокаЧто происходит
material = "бумага"строка внутри тела класса, но вне __init__ и без self — это и есть атрибут класса
book1.material, book2.materialоба объекта видят одно и то же значение, хотя ни один из них не задавал его сам
Book.materialатрибут класса доступен и через сам класс, без создания объекта — ещё одно отличие от атрибута экземпляра

Правило простое: если значение своё у каждого объекта — это атрибут экземпляра, задавайте через self в __init__. Если значение одно на всех — атрибут класса.

Методы с параметрами: объект, который умеет действовать

Пока что методы у вас только читали данные объекта. Но объект может ещё и изменять сам себя — например, счёт в банке умеет не только показывать баланс, но и пополняться, и списывать деньги. Такое поведение тоже описывают методом, только он принимает дополнительные параметры и меняет атрибуты через self.

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:
            return False
        self.balance -= amount
        return True

acc = BankAccount("Алиса", 1000)
acc.deposit(500)
print(acc.balance)          # 1500

success = acc.withdraw(2000)
print(success, acc.balance)  # False 1500 -- баланс не изменился
СтрокаЧто происходит
def __init__(self, owner, balance=0):параметр balance со значением по умолчанию — работает так же, как у обычных функций: не передали — будет 0
def deposit(self, amount):метод с дополнительным параметром amount — помимо обязательного self
self.balance += amountметод меняет атрибут того самого объекта, для которого его вызвали — именно поэтому нужен self, без него метод не знал бы, чей баланс менять
if amount > self.balance: return Falseобычная проверка через if, ровно такая же, как в функциях — классы не меняют то, что вы уже знаете про условия
acc.deposit(500)вызов метода: self Python подставляет сам (это acc), поэтому передаётся только amount=500

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

Забыли self в определении метода.

class Book:
    def description():   # нет self
        return "книга"

book = Book()
book.description()
# TypeError: description() takes 0 positional arguments but 1 was given

Python всё равно попытается подставить book первым аргументом при вызове — а свободного места под него в скобках нет. Первым параметром любого метода всегда должен идти self, даже если внутри метода он не используется.

Обратились к атрибуту без self внутри класса.

class Book:
    def __init__(self, title):
        title = title   # не self.title!

book = Book("1984")
print(book.title)
# AttributeError: 'Book' object has no attribute 'title'

Строка title = title создаёт обычную локальную переменную внутри __init__, которая исчезает, как только функция заканчивается. Чтобы значение осталось в объекте, слева обязательно должно стоять self.title.

Перепутали атрибут класса с атрибутом экземпляра для изменяемых данных.

Если атрибут класса задать один общий список или словарь на всех, все объекты будут делить один и тот же объект-данные, и изменение у одного отразится на всех остальных. Как правило, менять со временем нужно только атрибуты экземпляра — их задают в __init__ через self, а не в теле класса.

Следующий шаг

ООП от азов до PRO

Этот урок — только начало. В отдельном курсе вы разберёте ООП полностью: наследование, полиморфизм, инкапсуляцию, магические методы и паттерны проектирования на реальных проектах.

Наследование Полиморфизм Магические методы Паттерны
Смотреть курс на sudoteach.com

Что важно запомнить

  • Объект объединяет данные (атрибуты) и функции для работы с ними (методы)
  • Класс — чертёж; экземпляр (объект) — конкретная вещь, созданная по этому чертежу
  • Класс описывают через class ИмяКласса:, имя — в CamelCase
  • __init__ — конструктор, вызывается автоматически при создании объекта и задаёт начальные атрибуты
  • self — первый параметр каждого метода, ссылка на тот объект, для которого метод вызвали; при вызове его не передают
  • Атрибут экземпляра задаётся через self.имя в __init__ — свой у каждого объекта
  • Атрибут класса пишут в теле класса без self — общий для всех объектов
  • Метод — функция внутри класса; вызывается как объект.метод(аргументы)
  • Несколько объектов одного класса не мешают друг другу: изменение одного не затрагивает остальные

Проверьте себя

8 вопросов

Класс Book

Создайте класс Book с:

  • атрибутом класса material = "бумага";
  • конструктором __init__(self, title, author, pages), который сохраняет название, автора и количество страниц в атрибуты объекта;
  • методом description(self), который возвращает строку вида '1984' by George Orwell, 328 pages (кавычки вокруг названия — одинарные, как в примере).

Класс BankAccount

Premium