Методы класса и self
Мы уже умеем хранить данные в объекте через атрибуты. Но объекты должны не только хранить, но и делать что-то. Именно для этого существуют методы. И у каждого метода есть загадочный первый параметр — self. Сегодня разберёмся, что это такое и почему без него не обойтись.
🤔 Что такое метод и зачем он нужен?
В прошлом уроке мы научились создавать объекты с атрибутами. Например, у объекта 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
self.radius, возвращает площадь. Никаких аргументов — данные уже в объекте.self.radius. Один класс — одни данные — два разных действия над ними.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— конвенция, не ключевое слово. Но называть иначе не стоит.
🚀 Следующий урок: познакомимся с тем, как объекты взаимодействуют друг с другом — один объект как атрибут другого.