Python · Синтаксис · Начальный уровень
ООП: наследование и dunder-методы
Задачи по ООП в Python на наследование и специальные методы: дочерние классы, переопределение поведения, __str__, __len__, расширение базовых классов и работа с объектной моделью.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
Dunder-методы — __str__ и __len__
#Паттерны наследования и dunder-методов
#Быстрый справочник: наследование и dunder-методы
#Упражнения:
Animal и Dog через наследование.
#Создайте класс Animal , у создаваемых им объектов должен быть атрибут name . Значение в атрибут передается при инициализации объекта. То есть - объект описывает животное (любое) и хранит его кличку. Потом создайте класс Dog , который наследуется от Animal . В классе Dog добавьте метод __str__ , который возвращает строку в которой содержится тип животного (ахахах, извиняюсь, "тип животного" смешно звучит) и его кличка: "Dog: Rex" # пример возвращаемой строки Смысл такой: Базовый класс Animal хранит основное "поведение" объектов животных, а класс Dog детализирует под конкретный тип животного.
class Animal:
pass
class Dog(Animal):
pass
Решение
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __str__(self):
return f"Dog: {self.name}"
# Можно детализировать:
class Dog(Animal):
def __str__(self):
return f"Dog: {self.name}"
def tell(self):
print("Bark!!!") # "Гав" по англ. Собакам тоже нужен переводчик..
class Cat(Animal):
def __str__(self):
return f"Cat: {self.name}"
def tell(self):
print("Cats don't care about human commands!")
# self все равно нужно принять даже если в методе не используется.
Book со строковым представлением.
#Создайте класс Book с атрибутами для объектов title и pages . Добавьте метод __str__ , который возвращает: "Book: Python Basics (120 pages)" Можно добавить метод __len__ который будет возвращать количество страниц книги.
class Book:
pass
Решение
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __len__(self):
return self.pages
def __str__(self):
return f"Book: {self.title} ({self.pages} pages)"
User и Admin.
#Давайте похожее на 1е прогоним еще раз: Условие: Создайте класс User с атрибутом name . Создайте класс Admin , который наследуется от User . Переопределите метод __str__ , чтобы вывод был: "Admin: Alex" А у просто User выводилось через __str__ просто: "Alex"
class User:
pass
class Admin(User):
pass
Решение
class User:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
class Admin(User):
def __str__(self):
return f"Admin: {self.name}"
Коробка с предметами.
#Создайте класс Box с атрибутом items_count . Добавьте метод __len__ , который возвращает количество предметов. И, добавьте метод add и remove которые могут ложить и забирать объект из коробки. Это можно сделать просто по счетчику items_count (просто считать без сложностей), или сделать список предметов в коробке (строк) и подогнать класс для работы со списком (список атрибут объекта).
class Box:
pass
Решение
class Box:
def __init__(self, items_count):
self.items_count = items_count
def add(self, count=1):
self.items_count += count
def remove(self, count=1):
self.items_count -= count
def __len__(self):
return self.items_count
# Со списком посложнее и по другому:
class Box:
def __init__(self):
self.items = []
self.items_count = len(self.items)
def add(self, item):
self.items.append(item)
self.items_count = len(self.items)
def remove(self, item):
if item in self.items:
self.items.remove(item)
self.items_count = len(self.items)
else:
print("Кот из коробки говорит: Такого тут нет, досвидания!")
def __len__(self):
return self.items_count
Message со спам-флагом.
#Создайте класс Message с атрибутом text и атрибутом spam. В text текст сообщения передается при создании объекта, а атрибут spam изначально просто со значением None. Добавьте методы: - is_spam - который проверяет есть ли в тексте что-то из слов как критерий спама, возвращает True/False - при вызове метода is_spam , кроме возврата этим методом True/False, в атрибуте self.spam устанавливается соответсвтвующее значение. - и добаьте __len__ , который возвращает длину текста.
class Message:
pass
Решение
class Message:
def __init__(self, text):
self.text = text
self.spam = None
self.spam_triggers = ["куплю", "дорого"] # Да я не говорил об этом,
# но и не запрещал.
def is_spam(self):
if self.spam is not None: # ниже пояснение, это я уже "загнул"
return self.spam
for trigger in self.spam_triggers:
if trigger in self.text:
self.spam = True
return True
self.spam = False
return False
def __len__(self):
return len(self.text)
# Наличие self.spam после первого вызова is_spam позволит при повторных проверках
# не запускать механизм проверки, а просто глянуть в УЖЕ РАССЧИТАНЫЙ атрибут (оптимизация).
m = Message("Куплю старые телевизоры дорого!!!")
if m.is_spam(): # ведется реальная проверка
...
if m.is_spam(): # уже возьмется из self.spam
...
Employee и Manager.
#Создайте класс Employee (работник) с атрибутами name и salary . Сделайте метод у Employee который может менять зарплату. Создайте класс Manager , который наследуется от Employee . Переопределите метод __str__ так, чтобы вывод был: Manager John earns 5000 Убедитесь что метод изменения зарплаты работает и для Manager
class Employee:
pass
class Manager(Employee):
pass
Решение
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def change_salary(self, coef): # например коэффициент 1.1 - увеличить на 10%
self.salary = round(self.salary * coef) # без второго аргумента округлит до целого
class Manager(Employee):
def __str__(self):
return f"Manager {self.name} earns {self.salary}"
chel = Manager("Bob", 1000)
chel.change_salary(1.1)
print(chel) # Manager Bob earns 1100
Playlist и CustomPlaylist.
#Создайте класс Playlist с атрибутом songs (список). Добавьте методы add , remove - добавление/удаление песен. Добавьте метод reverse , который переворачивает список треков. Добавьте метод __len__ , который возвращает количество песен. Добавьте метод __bool__ - пустой или нет плейлист. А потом сделайте класс наследник CustomPlaylist в котором метод add будет позволять добавление дублей песен в плейлист. (да, первый соответственно не должен.)
class Playlist:
pass
Решение
class Playlist:
def __init__(self, songs): # 1 аргумент список с треками
self.songs = songs
def add(self, song):
if song not in self.songs:
self.songs.append(song)
def remove(self, song):
if song in self.songs:
self.songs.remove(song)
def reverse(self):
self.songs.reverse()
def __len__(self):
return len(self.songs)
def __bool__(self):
return bool(self.songs)
# или
# class Playlist:
# def __init__(self, *songs): # треки как отдельные аргументы
# self.songs = list(songs) # просто решил напомнить про *args
# # остальное такое же.
class CustomPlaylist(Playlist):
def add(self, song):
self.songs.append(song)
Именованный CustomPlaylist.
#Доработайте прошлый класс CustomPlaylist, добавив ему свой __init__, который берет базовое поведение __init__ родителя, но также у себя создает имя для плейлиста в атрибуте name
class CustomPlaylist(Playlist):
def __init__???
???
def add(self, song):
self.songs.append(song)
Решение
class CustomPlaylist(Playlist):
def __init__(self, songs, name):
super().__init__(songs)
self.name = name
def add(self, song):
self.songs.append(song)
def __str__(self):
return f"Playlist: {self.name}"
MagicBox со случайным предметом.
#Давайте вернемся к классу Box (код ниже есть), Давайте от него наследуемся и сделаем класс MagicBox , единственное добавленное поведение - - это при создании объекта коробки там будет появляться случайный предмет из списка. Список возможных случайных предметов определите где удобно это не важно, хоть в глобальной области. Заготовка:
from random import choice # функция случайного выбора из последовательности
class Box:
def __init__(self):
self.items = []
self.items_count = len(self.items)
def add(self, item):
self.items.append(item)
self.items_count = len(self.items)
def remove(self, item):
if item in self.items:
self.items.remove(item)
self.items_count = len(self.items)
else:
print("Кот из коробки говорит: Такого тут нет, досвидания!")
def __len__(self):
return self.items_count
things = ["Шапка", "Зонтик", "Кружка"]
class MagicBox(Box):
def __init__(self):
...
Решение
from random import choice # функция случайного выбора из последовательности
class Box:
def __init__(self):
self.items = []
self.items_count = len(self.items)
def add(self, item):
self.items.append(item)
self.items_count = len(self.items)
def remove(self, item):
if item in self.items:
self.items.remove(item)
self.items_count = len(self.items)
else:
print("Кот из коробки говорит: Такого тут нет, досвидания!")
def __len__(self):
return self.items_count
things = ["Шапка", "Зонтик", "Кружка"]
class MagicBox(Box):
def __init__(self):
super().__init__()
self.items.append(choice(things))
self.items_count = len(self.items)
# ИЛИ:
class MagicBox(Box):
def __init__(self):
super().__init__()
self.add(choice(things)) # так круче
x = MagicBox()
print(x.items)