$ sudo teach IT
МОДУЛЬ 2 · УРОК 3

Методы класса и self

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

⏱ ~35 минут 🎓 Для новичков 🐍 Python 3

🤔 Что такое метод и зачем он нужен?

В прошлом уроке мы научились создавать объекты с атрибутами. Например, у объекта user есть user.name = "Алекс" и user.age = 25. Это данные — они просто лежат внутри объекта и ждут.

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

Метод — это функция, которая живёт внутри класса и умеет работать с данными конкретного объекта. Если обычная функция — это инструмент сам по себе, то метод — инструмент, намертво прикреплённый к объекту.

💡 Аналогия: у вас есть пульт от телевизора. Кнопка «громкость +» — это метод пульта. Она не просто увеличивает какое-то абстрактное число — она увеличивает громкость именно этого телевизора, к которому пульт привязан. Вот в чём смысл метода: он знает, с каким конкретным объектом работает.

🔑 Что такое self на самом деле?

Вот тут начинается самое интересное. Когда вы создаёте метод в классе, первым параметром всегда пишется self. Новички часто воспринимают его как «магию» или «просто так принято». Но это не магия — это конкретная техническая необходимость.

self — это ссылка на сам объект. Это способ, которым метод «знает», с каким именно объектом он работает прямо сейчас.

Представьте: у вас есть класс Dog и два объекта — rex и buddy. Оба — собаки, оба могут «гавкать» через метод bark(). Но когда rex.bark() выводит имя — он должен вывести «Rex», а buddy.bark() — «Buddy». Как метод понимает, чьё имя печатать? Именно через self: когда вызывается rex.bark(), Python автоматически подставляет rex в параметр self. Когда buddy.bark() — подставляет buddy.

🎯 Ключевая идея: self — это не ключевое слово Python. Это просто имя первого параметра, которое программисты договорились использовать. Технически можно написать this, me или даже banana — и код будет работать. Но все в мире Python пишут self, и вы тоже должны — иначе коллеги решат, что вы делаете что-то странное.

Вот простой пример — метод, который приветствует от имени объекта:

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

    def bark(self):
        print(f"{self.name} говорит: Гав!")

rex = Dog("Rex")
buddy = Dog("Buddy")

rex.bark()    # Rex говорит: Гав!
buddy.bark()  # Buddy говорит: Гав!

Один и тот же метод bark — но результат разный, потому что self каждый раз указывает на другой объект. Это и есть сила методов.

⚙️ Как Python передаёт self автоматически

Обратите внимание на кое-что важное: когда вы вызываете rex.bark(), вы не передаёте никаких аргументов. Метод bark объявлен с одним параметром self — но при вызове скобки пустые. Как так?

Потому что Python делает это за вас автоматически. Когда вы пишете rex.bark(), Python «под капотом» превращает этот вызов в Dog.bark(rex) — то есть вызывает метод класса и явно передаёт объект rex как первый аргумент.

Давайте убедимся в этом — вот два абсолютно идентичных вызова:

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

    def bark(self):
        print(f"{self.name} говорит: Гав!")

rex = Dog("Rex")

# Обычный вызов — Python сам передаёт self
rex.bark()

# Эквивалентный вызов — мы передаём self вручную
Dog.bark(rex)

# Оба выводят одно и то же:
# Rex говорит: Гав!

Второй вариант — Dog.bark(rex) — в реальном коде не используется, он выглядит громоздко. Но полезно знать о нём: он раскрывает, что происходит внутри Python каждый раз, когда вы вызываете метод через точку.

📌 Запомните правило: obj.method(x, y) ≡ MyClass.method(obj, x, y). При вызове через точку Python сам подставляет объект первым аргументом. Именно поэтому в определении метода первый параметр всегда self — место, куда Python «кладёт» текущий объект.

📝 Методы с дополнительными параметрами

Метод — это обычная функция. Он может принимать любые дополнительные аргументы — после self. При вызове self по-прежнему передаётся автоматически, а остальные аргументы вы передаёте сами.

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

    def deposit(self, amount):
        self.balance += amount
        print(f"{self.owner}: пополнено на {amount}. Баланс: {self.balance}")

    def withdraw(self, amount):
        if amount > self.balance:
            print("Недостаточно средств!")
        else:
            self.balance -= amount
            print(f"{self.owner}: снято {amount}. Баланс: {self.balance}")

account = BankAccount("Алекс", 1000)
account.deposit(500)    # Алекс: пополнено на 500. Баланс: 1500
account.withdraw(200)   # Алекс: снято 200. Баланс: 1300
account.withdraw(5000)  # Недостаточно средств!

Метод deposit(self, amount) принимает два параметра, но вызывается с одним аргументом: account.deposit(500). Python подставляет account в self автоматически, а 500 попадает в amount.

🔗 Вызов одного метода из другого

Методы класса могут вызывать друг друга. Для этого внутри метода используется та же точечная нотация — но через self. Это логично: self — это и есть ваш объект, а у объекта есть все его методы.

Допустим, вы хотите, чтобы после каждого изменения баланса автоматически печаталась сводка. Вынесем печать в отдельный метод show_info() и будем вызывать его из других методов через self.show_info():

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

    def show_info(self):
        print(f"--- Счёт {self.owner}: {self.balance} руб. ---")

    def deposit(self, amount):
        self.balance += amount
        self.show_info()   # вызываем другой метод через self

    def withdraw(self, amount):
        if amount > self.balance:
            print("Недостаточно средств!")
        else:
            self.balance -= amount
            self.show_info()   # и здесь тоже

account = BankAccount("Мария", 500)
account.deposit(300)   # --- Счёт Мария: 800 руб. ---
account.withdraw(100)  # --- Счёт Мария: 700 руб. ---

💡 Почему именно self.show_info(), а не просто show_info()? Потому что show_info — это не обычная функция, она принадлежит объекту. Без self. Python будет искать её в глобальной области видимости и не найдёт — получите ошибку NameError: name 'show_info' is not defined. Всегда обращайтесь к методам своего объекта через self.

🚫 Что будет, если забыть self?

Это очень распространённая ошибка у новичков. Давайте намеренно допустим её и посмотрим, что произойдёт:

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

    def bark():  # ← забыли self!
        print("Гав!")

rex = Dog("Rex")
rex.bark()
# TypeError: bark() takes 0 positional arguments but 1 was given

Сообщение об ошибке кажется странным: мы вызвали rex.bark() без аргументов, а Python говорит «передан 1 аргумент, а ожидалось 0». Откуда взялся этот 1 аргумент?

Всё верно: Python автоматически передаёт объект rex как первый аргумент. Метод объявлен без параметров — вот и конфликт. Python честно говорит: «Я пытаюсь передать объект как self, но у тебя нет параметра для этого».

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

⭕ Демо: класс Circle

Давайте соберём всё вместе в реальном примере. Создадим класс Circle, который хранит радиус и умеет вычислять площадь и длину окружности. Для вычислений используем константу math.pi из стандартной библиотеки Python.

Формулы: площадь = π × r², длина окружности = 2 × π × r.

import math

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

    def circumference(self):
        return 2 * math.pi * self.radius

    def describe(self):
        a = self.area()           # вызов метода из метода
        c = self.circumference()  # вызов метода из метода
        print(f"Круг с радиусом {self.radius}:")
        print(f"  Площадь:           {a:.2f}")
        print(f"  Длина окружности:  {c:.2f}")


# Создаём несколько окружностей
small = Circle(5)
large = Circle(10)

small.describe()
# Круг с радиусом 5:
#   Площадь:           78.54
#   Длина окружности:  31.42

large.describe()
# Круг с радиусом 10:
#   Площадь:           314.16
#   Длина окружности:  62.83

# Можно вызывать методы напрямую
print(small.area())          # 78.53981633974483
print(large.circumference()) # 62.83185307179586
📐
area()
Читает self.radius, возвращает площадь. Никаких аргументов — данные уже в объекте.
📏
circumference()
Тоже читает self.radius. Один класс — одни данные — два разных действия над ними.
🔗
describe()
Вызывает self.area() и self.circumference() — пример межметодного взаимодействия.

🆚 Метод vs обычная функция

Чем метод принципиально отличается от обычной функции? Давайте сравним:

🔧 Обычная функция
  • Существует сама по себе
  • Не привязана к объекту
  • Вызывается просто по имени
  • Данные получает через аргументы
⚙️ Метод класса
  • Принадлежит классу/объекту
  • Всегда привязан к объекту
  • Вызывается через точку: obj.method()
  • Получает данные объекта через self
# Обычная функция — данные нужно передать явно
def area_of_circle(radius):
    return math.pi * radius ** 2

result = area_of_circle(5)  # передаём радиус явно

# Метод — данные уже в объекте
c = Circle(5)
result = c.area()  # радиус не передаём — он в self.radius

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

✅ Итоги урока

  • Метод — это функция внутри класса, которая работает с данными конкретного объекта.
  • self — ссылка на текущий объект. Через self метод читает атрибуты и вызывает другие методы.
  • Python автоматически передаёт объект в self при вызове obj.method() — писать его вручную не нужно.
  • obj.method(x) полностью эквивалентно MyClass.method(obj, x).
  • Из одного метода можно вызвать другой через self.other_method().
  • Забыть self в объявлении метода → TypeError при вызове.
  • Имя self — конвенция, не ключевое слово. Но называть иначе не стоит.

🚀 Следующий урок: познакомимся с тем, как объекты взаимодействуют друг с другом — один объект как атрибут другого.