Занятие № 11. Описание типов пользователя

Цель: научиться создавать собственные классы и объекты.

Теория

Объекты и классы

Все значения в Python являются объектами определённых классов. Чтобы узнать экземпляром какого класса является значение, используем функцию type():

print(type(42))
print(type(3.14))
print(type("Python"))
print(type([1, 1, 2, 3, 5]))
print(type({1: 'I', 2: 'II'}))

      
print(type(42))
print(type(3.14))
print(type("Python"))
print(type([1, 1, 2, 3, 5]))
print(type({1: 'I', 2: 'II'}))

Даже функции являются объектами класса function:

def hello():
    print("Привет, мир!")
    
print(type(hello))

      
def hello():
    print("Привет, мир!")
    
print(type(hello))

Поэтому важно понимать, как можно создать собственный тип данных, если нет подходящего среди стандартных.

Описание класса

Создание собственного класса рассмотрим на примере описания собственного типа Point, который будет хранить информацию о точке не плоскости. Любой объект хранит в себе набор членов класса. К ним относятся поля - состояние объекта и методы - функции, которые меняют состояние класса.

Простейшее описание класса выглядит так:

class Point:
    pass

Сейчас в классе Point нет ни полей ни методов.

Чтобы создать экземпляр класса или объект этого типа, необходимо записать имя класса с круглыми скобками:

a = Point()

print(type(a))

Программа скажет нам, что переменная a ссылается на объект класса <class '__main__.Point'>.

Для обращения к полям и методам класса используется оператор . точка. Вы использовали подобную запись когда, например, добавляли элемент к списку, записывая инструкцию <список>. x append(<элемент>).

Последовательно запускайте интерактивные примеры с кодом. Следующий фрагмент кода может зависеть от предыдущего.

Добавить поля к объекту можно также с помощью их записи через точку:

class Point:
    pass
    
a = Point()
a.x =  4.3  # добавляем поле x
a.y = -2.1  # добавляем поле y

# получаем значения из полей x и y
print(a.x, a.y) 

      
class Point:
    pass
    
a = Point()
a.x =  4.3  # добавляем поле x
a.y = -2.1  # добавляем поле y

# получаем значения из полей x и y
print(a.x, a.y) 

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

Каждый раз задавать значения для полей таким образом неудобно, поэтому объявим функцию init(), которая в качестве аргументов будет принимать объект типа Point и значения полей x и y:

class Point:
    pass

def init(p, x, y):
    # задаём начальные значения полей
    p.x = x
    p.y = y
    
b = Point()     # создаём новый объект типа Point
init(b, 3, 5)   # инициализируем начальные значения полей
print(b.x, b.y)

      
class Point:
    pass

def init(p, x, y):
    # задаём начальные значения полей
    p.x = x
    p.y = y
    
b = Point()     # создаём новый объект типа Point
init(b, 3, 5)   # инициализируем начальные значения полей
print(b.x, b.y)

Python позволяет поместить функцию инициализации полей класса внутрь описания класса. Для этого в класс добавляется метод __init__(). Первый параметр метода будет ссылкой на объект, который нужно инициализировать. Этот параметр может иметь произвольное имя, но принято называть его self:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

Теперь при создании объекта класса можно указать начальные значения полей класса:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

c = Point(-3.4, 11)
print(c.x, c.y)

      
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

c = Point(-3.4, 11)
print(c.x, c.y)

Посмотрите на последовательное выполнение кода, в котором создаётся объект класса Point.

После выполнения кода в строке x будет вызван метод __init__().

Опишем ещё одну функцию origin_dist() с помощью которой рассчитаем расстояние от начала координат до заданной точки:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def origin_dist(p):
    return (p.x ** 2 + p.y ** 2) ** 0.5

tochka = Point(0, 32)
dist = origin_dist(tochka)
print(dist)

      
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def origin_dist(p):
    return (p.x ** 2 + p.y ** 2) ** 0.5

tochka = Point(0, 32)
dist = origin_dist(tochka)
print(dist)

Функцию origin_dist() так же можно оформить как метод класса Point. Как и в случае с __init__(), первый параметр любого метода класса - это ссылка на объект, который нужно обработать:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # метод расчёта расстояния до точки (0,0)
    def origin_dist(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
tochka = Point(0, 32)
dist = tochka.origin_dist() # вызываем метод класса
print(dist)

      
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # метод расчёта расстояния до точки (0,0)
    def origin_dist(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
tochka = Point(0, 32)
dist = tochka.origin_dist() # вызываем метод класса
print(dist)

Посмотрите на последовательное выполнение кода в котором используется метод origin_dist() класса Point.

В записи вызова метода tochka.origin_dist() в скобках не указываются аргументы. Подобная запись преобразуется Python в следующий эквивалентный код:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # метод расчёта расстояния до точки (0,0)
    def origin_dist(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
tochka = Point(0, 32)

dist = Point.origin_dist(tochka)
print(dist)

      
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # метод расчёта расстояния до точки (0,0)
    def origin_dist(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    
tochka = Point(0, 32)

dist = Point.origin_dist(tochka)
print(dist)

Специальные методы класса

Когда мы используем функцию print() и выводим значение выражения того или иного типа, это значение преобразуется в строку. Если попробовать вывести объект класса Point, то получим следующий результат:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
c = Point(-9, 16)

print(c)

      
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
c = Point(-9, 16)

print(c)

Результат не показывает содержимого полей класса. Чтобы определить, как преобразовать объект класса в строку, в его определении нужно добавить специальный метод __str__(). Добавим этот метод к объявлению класса Point, чтобы выводить координаты точки:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def origin_dist(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
        
    def __str__(self):
        return f"x={self.x}, y={self.y}"
    
v = Point(-9, 16)
print(v) # выводим объект v на экран; будет вызван метод __str__()

      
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def origin_dist(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
        
    def __str__(self):
        return f"x={self.x}, y={self.y}"
    
v = Point(-9, 16)
print(v) # выводим объект v на экран; будет вызван метод __str__()

Специальные методы позволяют реализовать в собственном классе функции доступные для других типов объектов.

Примеры

Пример № 1 (ex01.py)

Опишите класс MSDice который реализует многогранную игральную кость. Описание класса содержит количество граней и текущее значение.

from random import randint

class MSDice:
    """Описание класса"""

    def __init__(self, sides):
        """Конструктор класса"""
        self.sides = sides
        self.value = 1

    def roll(self):
        """Бросок кубика"""
        self.value = randint(1, self.sides)

    def getValue(self):
        """Получаем значение, выпавшее на кубике"""
        return self.value

    def setValue(self, value):
        """Меняем значение на произвольное"""
        self.value = value

die20 = MSDice(20) # создаём игровой кубик с 20 гранями
print(die20.getValue()) # выводим текущее значение на кубике
die20.roll() # "бросаем" кубик
print(die20.getValue()) # выводим текущее значение на кубике
die20.setValue(8) # меняем значение на 8
print(die20.getValue())

      
from random import randint

class MSDice:
    """Описание класса"""

    def __init__(self, sides):
        """Конструктор класса"""
        self.sides = sides
        self.value = 1

    def roll(self):
        """Бросок кубика"""
        self.value = randint(1, self.sides)

    def getValue(self):
        """Получаем значение, выпавшее на кубике"""
        return self.value

    def setValue(self, value):
        """Меняем значение на произвольное"""
        self.value = value

die20 = MSDice(20) # создаём игровой кубик с 20 гранями
print(die20.getValue()) # выводим текущее значение на кубике
die20.roll() # "бросаем" кубик
print(die20.getValue()) # выводим текущее значение на кубике
die20.setValue(8) # меняем значение на 8
print(die20.getValue())
Визуализация работы программы

За пошаговым выполнением программы можно проследить, перейдя по следующей ссылке.

Задания для самостоятельной работы

Задание № 1 (sam01.py)

Опишите класс Robot, содержащий одно поле model и метод sayHi(name). Метод должен поприветствовать человека, чьё имя указано в параметре name.

Для написания программы используйте следующий шаблон. Вместо комментариев в методе __init__ и say_Hi напишите их реализацию.

Шаблон:

class Robot:
    def __init__(self, model):
        # сохраняем модель робота в поле с именем model

    def sayHi(self, name):
        # метод должен вывести сообщение
        # Привет, <name>. Меня зовут <model>.

# следующий код оставьте без изменений
bender = Robot("Бендер")
bender.sayHi("Фрай")

term = Robot("T-800")
term.sayHi("Сара Конор")

Пример:

Привет, Фрай. Меня зовут Бендер
Привет, Сара Конор. Меня зовут T-800

Задание № 2 (sam02.py)

Опишите класс Student. Класс должен содержать следующие поля:

  • last_name - фамилия студента (строка)
  • course - номер курса, на котором студент учится (целое число)

Класс должен содержать следующие методы:

  • __init__(last_name, course) - конструктор, который создаёт объект и сохраняет нужные поля
  • printInfo() - выводит на экран информацию о студенте (см. пример)
  • getCourse() -возвращает значение поля course
  • setCourse(new_course) - меняет текущий номер курса на new_course

Для написания программы используйте следующий шаблон.

Шаблон:

# тут расположите описание класса Student


# следующий код оставьте без изменений
stud1 = Student("Иванов", 2)
stud1.printInfo()
print("Номер курса:", stud1.getCourse())
print("Переводим на следующий курс")
stud1.setCourse(3)
print("Номер курса:", stud1.getCourse())

Пример:

Фамилия: Иванов; учится на 2 курсе
Номер курса: 2
Переводим на следующий курс
Номер курса: 3

Задание № 3 (sam03.py)

Создайте класс Player, описывающий игрока у которого есть следующие поля:

  • nickname - имя игрока (строка)
  • exp_points - количество очков опыта (целое число); начальное значение - 0
  • inventory - список предметов, которые есть у игрока (список); начальное значение - []

Класс должен содержать следующие методы:

  • __init__(nickname) - конструктор, принимающий в качестве параметра имя игрока. Также конструктор инициализирует поля exp_points=0 и inventory=[]
  • __str__ - преобразование объекта в строку (формат вывода см. в примере)
  • addExp(exp) - добавить игроку exp очков опыта
  • addItem(item) - добавить к списку inventory предмет item (строка)
  • removeItem(item) - удалить из списка inventory предмет с именем item (строка)

Для написания программы используйте следующий шаблон.

Шаблон:

# тут расположите описание класса Player


# следующий код оставьте без изменений
novice = Player("MegaPro") # создание объекта
print(novice) # вывод информации об объекте (будет вызван метод __str__)
novice.addExp(100) # добавляем 100 очков опыта
novice.addItem("броня") # добавляем предмет к инвентарю
novice.addItem("меч") # добавляем предмет к инвентарю
print(novice) # вывод информации об объекте
novice.addExp(50) # добавляем 50 очков опыта
novice.removeItem("броня") # удаляем предмет из инвентаря
print(novice)

Пример:

MegaPro - 0 exp. - []
MegaPro - 100 exp. - ['броня', 'меч']
MegaPro - 150 exp. - ['меч']

Задание № 4 (sam04.py)

С помощью ООП реализуйте модель корзины для магазина. Объект с товаром содержит его название и цену. Корзина содержит электронную почту заказчика и список товаров. Опишите два класса - Item (Товар) и Cart (Корзина).

Поля класса Item:

  • title - название товара (строка)
  • price - цена товара (целое число)

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

  • __init__(title, price) - конструктор класса
  • getPrice() - возвращает цену товара
  • __str__() - Преобразование объекта в строку (формат вывода см. в примере)

Поля класса Cart:

  • email - электронная почта заказчика (строка)
  • items - список товаров в корзине (список)

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

  • __init__(email) - конструктор класса; поле items инициализируется пустым списком []
  • addItem(item) - добавить заказ к списку, который хранится в поле items
  • getTotalPrice() - возвращает общую стоимость всех товаров в корзине
  • printOrder() - выводит на экран список всех товаров в корзине (формат вывода см. в примере)

Для написания программы используйте следующий шаблон.

Шаблон:

# тут расположите описание класса Item

# тут расположите описание класса Cart


# следующий код оставьте без изменений
apple = Item("Яблоко", 20) # создаём товар
pen = Item("Ручка", 10) # создаём товар
print(apple) # выводим информацию о товаре (будет вызван метод __str__)
print("Цена товара:", apple.getPrice()) # получаем цену товара

my_cart = Cart("user@mail.com") # создаём корзину
my_cart.addItem(apple) # добавляем первый товар в корзину
my_cart.addItem(pen) # добавляем второй товар в корзину
my_cart.printOrder() # выводит список товаров в корзине
print("Сумма:", my_cart.getTotalPrice()) # получаем общую стоимость заказа

Пример:

Яблоко - 20 руб.
Цена товара: 20
Товары в корзине:
Яблоко - 20 руб.
Ручка - 10 руб.
Сумма: 30