Skip to content

Latest commit

 

History

History
841 lines (527 loc) · 17.5 KB

File metadata and controls

841 lines (527 loc) · 17.5 KB

Урок 3. Методы класса и параметр self

На предыдущем занятии мы познакомились с классами и объектами и узнали, что класс — это шаблон, по которому создаются объекты.

Также мы выяснили, что класс может содержать:

  • атрибуты (данные) — свойства объекта
  • методы (функции) — действия объекта

Именно методы позволяют объектам что-то делать.

В этом уроке мы подробно разберём:

  • что такое методы класса
  • как они вызываются
  • зачем нужен параметр self
  • как методы работают с конкретным объектом

Это одна из ключевых тем ООП. Понимание self определяет, насколько легко вы будете писать объектно-ориентированный код.


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

Метод — это функция, объявленная внутри класса.

Он описывает действие, которое может выполнять объект.

Принято придерживаться простого правила именования:

Тип Примеры
Атрибуты (данные) color, size, x, y
Методы (действия) set_value, get_param, start, stop

Другими словами:

данные — существительные методы — глаголы

Это не строгое правило Python, но оно делает код гораздо понятнее.


Первый пример метода

Создадим простой класс Point.

class Point:
    color = "red"
    circle = 2

    def set_coords(self):
        print("Вызов метода set_coords")

Разберём код.

Объявление класса:

class Point:

Атрибуты класса:

color = "red"
circle = 2

Метод класса:

def set_coords(self):

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

Но пока мы не собираемся передавать в нашу функцию аргументы. Давайте уберем этот параметр.

def set_coords():

Вызов метода через класс

Попробуем вызвать метод напрямую через класс.

Point.set_coords()

Результат:

Вызов метода set_coords

Метод успешно выполнен.

Механизм вызова очень простой:

ИмяКласса.имя_метода()

Важно помнить:

метод — это обычная функция, которая хранится внутри класса.


Создание объекта

Теперь создадим объект класса.

pt = Point()

Теперь переменная pt хранит экземпляр класса.

Схематично это выглядит так:

Класс Point
   │
   ├── color
   ├── circle
   └── set_coords()

        ↓

Экземпляр pt

Объект может обращаться к атрибутам и методам класса.

Попробуем получить метод через объект.

pt.set_coords

Python вернёт ссылку на функцию.

Но если попытаться вызвать метод:

pt.set_coords()

возникнет ошибка.

TypeError: set_coords() takes 0 positional arguments but 1 was given

Почему так произошло?


Что происходит при вызове метода через объект

Когда метод вызывается через экземпляр класса, Python автоматически передаёт ссылку на объект.

То есть фактически происходит следующее.

Мы пишем:

pt.set_coords()

А Python выполняет:

Point.set_coords(pt)

То есть объект передаётся первым аргументом.

Именно поэтому в методе должен быть параметр self.


Параметр self

self — это ссылка на объект, который вызвал метод.

Исправим наш метод.

class Point:
    color = "red"
    circle = 2

    def set_coords(self):
        print("Вызов метода set_coords", self)

Теперь вызов:

pt = Point()
pt.set_coords()

выведет примерно следующее:

Вызов метода set_coords <__main__.Point object at 0x000001>

То есть self — это сам объект pt.


Схема работы self

Когда вызывается метод:

pt.set_coords()

происходит следующее:

pt.set_coords()

   ↓

Point.set_coords(pt)

И внутри метода:

self = pt

Вызов метода через класс

Теперь попробуем снова вызвать метод через класс.

Point.set_coords()

Получим ошибку:

TypeError: missing 1 required positional argument: 'self'

Потому что Python не знает, какой объект передать.

Но мы можем передать его вручную.

Point.set_coords(pt)

И метод снова выполнится.

Это тот же самый вызов, который Python делает автоматически.


Зачем нужен self

Теперь главный вопрос.

Почему Python использует такую систему?

Ответ очень простой.

Методы класса не копируются в каждый объект.

Они существуют в одном экземпляре внутри класса.

Класс Point
   │
   └── метод set_coords()

Объекты
   ├── pt
   ├── pt2
   └── pt3

Все объекты используют один и тот же метод.

Но благодаря self метод знает:

с каким объектом он сейчас работает.


Работа с данными объекта через self

Теперь создадим настоящий полезный метод.

class Point:
    color = "red"
    circle = 2

    def set_coords(self, x, y):
        self.x = x
        self.y = y

Разберём код.

Метод принимает координаты:

def set_coords(self, x, y)

Создаётся атрибут объекта x:

self.x = x

Создаётся атрибут объекта y.

self.y = y

Использование метода

pt = Point()

pt.set_coords(1, 2)

Теперь посмотрим внутренние данные объекта.

print(pt.__dict__)

Результат:

{'x': 1, 'y': 2}

Это пространство имён объекта.


Что хранится в __dict__

Каждый объект имеет словарь атрибутов.

pt.__dict__

Схема:

pt

{
    "x": 1,
    "y": 2
}

Атрибуты создаются в момент присвоения.

То есть строка

self.x = x

фактически делает следующее:

pt.__dict__["x"] = 1

Независимость объектов

Создадим второй объект.

pt2 = Point()

pt2.set_coords(10, 20)

Посмотрим данные:

print(pt.__dict__)
print(pt2.__dict__)

Результат:

{'x': 1, 'y': 2}
{'x': 10, 'y': 20}

Объекты не влияют друг на друга.

Именно благодаря self метод знает, в какой объект записывать данные.


Метод получения данных

Добавим ещё один метод.

class Point:

    def set_coords(self, x, y):
        self.x = x
        self.y = y

    def get_coords(self):
        return (self.x, self.y)

Использование:

pt = Point()

pt.set_coords(3, 5)

print(pt.get_coords())

Результат:

(3, 5)

Метод использует данные объекта через self.


Важная возможная ошибка

Частая ошибка — забыть self.

Например:

def set_coords(x, y):
    self.x = x

Ошибка:

NameError: name 'self' is not defined

Почему?

Потому что self не создаётся автоматически.

Это обычный параметр функции, который мы обязаны объявить.

Правильный вариант:

def set_coords(self, x, y):

Методы — это атрибуты класса

Интересный факт:

методы — это обычные атрибуты класса, которые хранят функции.

Можно получить метод через getattr.

pt = Point()

method = getattr(pt, "get_coords")

print(method)

Результат:

<bound method Point.get_coords of <Point object>>

Теперь можно вызвать его.

print(method())

Но на практике почти всегда используется синтаксис:

pt.get_coords()

Он проще и читается лучше.


Итог

В этом уроке мы разобрали фундаментальную часть ООП.

Методы — это функции внутри класса, которые описывают поведение объектов.

Главная особенность методов — параметр self.

Он:

  • содержит ссылку на текущий объект
  • позволяет работать с его данными
  • обеспечивает независимость объектов

Без self методы не смогли бы понимать, с каким объектом они работают.


Вопросы

  1. Что такое метод класса?
  2. Чем методы отличаются от обычных функций?
  3. Почему методы обычно называют глаголами?
  4. Что происходит при вызове метода через объект?
  5. Что хранится в параметре self?
  6. Почему при вызове метода через класс возникает ошибка без self?
  7. Почему методы не копируются в каждый объект?
  8. Что хранится в __dict__ объекта?
  9. Почему объекты одного класса могут иметь разные значения атрибутов?
  10. Почему self нужно объявлять явно?

Задачи

Задача 1

Объявите класс MediaPlayer с двумя методами:

  • open(file) — для открытия медиа-файла с именем file. Метод должен создавать атрибут объекта filename, содержащий имя файла.

  • play() — для воспроизведения файла. Метод должен выводить строку: Воспроизведение <имя файла>

Создайте два объекта media1 и media2 и вызовите:

  • media1.open("filemedia1")
  • media2.open("filemedia2")

После этого вызовите метод play() для каждого объекта.

Ожидаемый результат:

Воспроизведение filemedia1
Воспроизведение filemedia2

Задача 2

Объявите класс Lamp, который моделирует лампу.

В классе должны быть два метода:

turn_on() — включает лампу (устанавливает атрибут state = "on")

turn_off() — выключает лампу (устанавливает атрибут state = "off")

Создайте объект lamp.

Последовательно выполните:

lamp.turn_on()
lamp.turn_off()

После этого выведите значение атрибута state.

Ожидаемый вывод:

off

Задача 3

Объявите класс Graph со следующими элементами.

Атрибут класса:

LIMIT_Y = [0, 10]

Методы:

  • set_data(data). Принимает список чисел и сохраняет его в атрибуте объекта data.

  • draw(). Выводит числа из списка data, которые находятся в диапазоне LIMIT_Y.

Границы диапазона включаются.

Числа должны выводиться в одну строку через пробел.

Создайте объект:

graph_1

Передайте ему данные:

[10, -5, 100, 20, 0, 80, 45, 2, 5, 7]

После этого вызовите метод draw().

Ожидаемый вывод:

10 0 2 5 7

Задача 4

Создайте класс Cart, который моделирует корзину покупок.

В классе должны быть методы:

  • add(product) — добавляет товар product в корзину.

  • remove(product) — удаляет товар из корзины, если он там есть.

  • show() — выводит все товары корзины через пробел.

Товары должны храниться в атрибуте объекта items (список).

Создайте объект cart.

Выполните:

cart.add("apple")
cart.add("milk")
cart.add("bread")
cart.remove("milk")
cart.show()

Ожидаемый вывод:

apple bread

Задача 5

Дан класс Translator.

В нём объявлены три метода:

  • add(self, eng, rus)
  • remove(self, eng)
  • translate(self, eng)

В объекте класса должен храниться словарь переводов:

{
   "english_word": ["перевод1", "перевод2"]
}

Методы должны работать следующим образом.

  • add(eng, rus)

    Добавляет перевод слова.

    Если слово уже существует, новый перевод добавляется как синоним.

    Если перевод уже есть — добавлять его повторно нельзя.

  • remove(eng)

    Удаляет слово из словаря.

  • translate(eng)

    Возвращает список переводов.

Создайте объект:

tr

Добавьте переводы:

tree - дерево
car - машина
car - автомобиль
leaf - лист
river - река
go - идти
go - ехать
go - ходить
milk - молоко

Удалите слово:

car

Выведите перевод слова:

go

Ожидаемый вывод:

идти ехать ходить

Задача 6

Имеется класс StreamReader, который считывает строки.

class StreamReader:

    FIELDS = ('id', 'title', 'pages')

    def readlines(self):

        lst_in = input().split()

        sd = StreamData()

        res = sd.create(self.FIELDS, lst_in)

        return sd, res

Необходимо объявить класс StreamData с методом:

create(self, fields, lst_values)

Метод должен:

  1. Получать кортеж названий полей fields
  2. Получать список значений lst_values
  3. Создавать внутри объекта атрибуты с именами из fields

Например:

fields = ('id', 'title', 'pages')
lst_values = ['1', 'Python', '500']

В объекте должны появиться атрибуты:

id = '1'
title = 'Python'
pages = '500'

Если количество полей и значений не совпадает, метод должен вернуть:

False

И не создавать атрибуты.

Если всё прошло успешно — вернуть:

True

Предыдущий урок | Следующий урок