Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Напомним, что эта статья – продолжение темы ООП в Python: предыдущая часть была посвящена инкапсуляции и наследованию.
Абстракция
Одна из основных целей использования абстракции в ООП – повышение гибкости и упрощение разработки. Абстрактный подход помогает создавать интерфейсы и классы, которые определяют только те свойства и методы, которые необходимы для выполнения определенной задачи. Это позволяет создавать более гибкие и масштабируемые приложения, которые легко поддаются изменению и расширению.
Предположим, что нам нужно написать программу, которая работает с графическими объектами разных типов. Для решения этой задачи удобно создать абстрактный класс Shape (фигура), определяющий абстрактные методы, которые могут быть использованы для работы с любой фигурой. Затем мы можем создать конкретные классы для конкретных типов фигур – окружность, квадрат, треугольник и т.д., которые расширяют базовый класс Shape. При этом мы можем использовать только те свойства и методы, которые необходимы для выполнения конкретной задачи, игнорируя детали реализации, которые не имеют значения в данном контексте.
Абстрактный подход помогает эффективно решать ряд сложных задач:
- Позволяет выделять существенные характеристики объекта, игнорируя все незначительные детали.
- Принуждает подклассы к реализации конкретных методов или к выполнению определенных требований путем определения абстрактных методов или свойств. Таким образом, абстракция позволяет определять общие интерфейсы для классов, но при этом гарантирует, что каждый подкласс будет реализовывать свою версию этих методов или свойств.
- Позволяет создавать общие модели объектов, которые могут использоваться для создания конкретных объектов.
- Упрощает работу со сложными системами, которые включают множество взаимодействующих компонентов, и позволяет создавать расширяемые, модульные приложения.
Абстрактные классы в Python
Для работы с абстрактными классами в Python используют модуль abc. Он предоставляет:
- abc.ABC – базовый класс для создания абстрактных классов. Абстрактный класс содержит один или несколько абстрактных методов, то есть методов без определения (пустых, без кода). Эти методы необходимо переопределить в подклассах.
- abc.abstractmethod – декоратор, который указывает, что метод является абстрактным. Этот декоратор применяется к методу внутри абстрактного класса. Класс, который наследует свойства и методы от абстрактного класса, должен реализовать все абстрактные методы, иначе он также будет считаться абстрактным.
Рассмотрим пример абстрактного класса Book:
from abc import ABC, abstractmethod
class Book(ABC):
def __init__(self, title, author):
self.title = title
self.author = author
@abstractmethod
def get_summary(self):
pass
class Fiction(Book):
def get_summary(self):
print(f'"{self.title}" - роман в стиле исторический фикшн, автор - {self.author}')
class NonFiction(Book):
def get_summary(self):
print(f'"{self.title}" - книга в стиле нон фикшн, автор - {self.author}')
class Poetry(Book):
pass
Класс Book имеет абстрактный метод get_summary(). Два подкласса Book (Fiction и NonFiction) реализуют метод get_summary(), а третий подкласс Poetry – нет. Когда мы создаем экземпляры Fiction и NonFiction и вызываем их методы get_summary(), получаем ожидаемый результат:
fiction_book = Fiction("Террор", "Дэн Симмонс")
nonfiction_book = NonFiction("Как писать книги", "Стивен Кинг")
fiction_book.get_summary()
nonfiction_book.get_summary()
Вывод:
"Террор" - роман в стиле исторический фикшн, автор - Дэн Симмонс
"Как писать книги" - книга в стиле нон фикшн, автор - Стивен Кинг
А вот вызов Poetry приведет к ошибке, поскольку в этом подклассе метод get_summary() не реализован:
poetry_book = Poetry("Стихотворения", "Борис Пастернак")
Вывод:
TypeError: Can't instantiate abstract class Poetry with abstract methods get_summary
Приведенный выше пример показывает, что семейство родственных классов (Fiction и NonFiction в нашем случае) может иметь общий интерфейс (метод get_summary()), но реализация этого интерфейса может быть разной. Мы также убедились, что любой подкласс Book должен реализовать метод get_summary(), чтобы обеспечить согласованную, безошибочную работу приложения.
Теперь рассмотрим чуть более сложный пример, который продемонстрирует, как можно комбинировать абстракцию с другими концепциями ООП. Определим абстрактный класс Recipe (рецепт), который имеет абстрактный метод cook(). Затем создадим три подкласса Entree, Dessert и Appetizer (основное блюдо, десерт и закуска). Entree и Dessert имеют свои собственные методы cook(), в отличие от Appetizer и PartyMix. PartyMix (орешки, чипсы, крекеры) является подклассом Appetizer и имеет свою реализацию cook():
from abc import ABC, abstractmethod
class Recipe(ABC):
@abstractmethod
def cook(self):
pass
class Entree(Recipe):
def __init__(self, ingredients):
self.ingredients = ingredients
def cook(self):
print(f"Готовим на медленном огне смесь ингредиентов ({', '.join(self.ingredients)}) для основного блюда")
class Dessert(Recipe):
def __init__(self, ingredients):
self.ingredients = ingredients
def cook(self):
print(f"Смешиваем {', '.join(self.ingredients)} для десерта")
class Appetizer(Recipe):
pass
class PartyMix(Appetizer):
def cook(self):
print("Готовим снеки - выкладываем на поднос орешки, чипсы и крекеры")
В этом примере наряду с абстракцией используются концепции полиморфизма и наследования.
Наследование заключается в том, что подклассы Entree, Dessert и PartyMix наследуют абстрактный метод cook() от абстрактного базового класса Recipe. Это означает, что все они имеют ту же сигнатуру (название и параметры) метода cook(), что и абстрактный метод, определенный в классе Recipe.
Полиморфизм проявляется в том, что каждый подкласс класса Recipe реализует метод cook() по-разному. Например, Entree реализует cook() для вывода инструкций по приготовлению основного блюда на медленном огне, а Dessert реализует cook() для вывода инструкций по смешиванию ингредиентов десерта. Эта разница в реализации является примером полиморфизма, когда различные объекты могут рассматриваться как объекты, которые относятся к одному типу, но при этом ведут себя по-разному:
entree = Entree(["курица", "рис", "овощи"])
dessert = Dessert(["мороженое", "шоколадные чипсы", "мараскиновые вишни"])
partymix = PartyMix()
entree.cook()
dessert.cook()
partymix.cook()
Результат:
Готовим на медленном огне смесь ингредиентов (курица, рис, овощи) для основного блюда
Смешиваем мороженое, шоколадные чипсы, мараскиновые вишни для десерта
Готовим снеки - выкладываем на поднос орешки, чипсы и крекеры
Вызов метода cook() для подкласса Appetizer приведет к ожидаемой ошибке:
appetizer = Appetizer()
appetizer.cook()
Результат:
TypeError: Can't instantiate abstract class Appetizer with abstract methods cook
Полиморфизм
Полиморфизм позволяет обращаться с объектами разных классов так, как будто они являются объектами одного класса. Реализовать полиморфизм можно через наследование, интерфейсы и перегрузку методов. Этот подход имеет несколько весомых преимуществ:
- Позволяет использовать различные реализации методов в зависимости от типа объекта, что делает код более универсальным и удобным для использования.
- Уменьшает дублирование кода – можно написать одну функцию для работы с несколькими типами объектов.
- Позволяет использовать общие интерфейсы и абстракции для работы с объектами разных типов.
- Обеспечивает гибкость и расширяемость – можно добавлять новые типы объектов без необходимости изменять существующий код. Это дает возможность разработчикам встраивать новые функции в программу, не нарушая ее существующую функциональность.
Полиморфизм тесно связан с абстракцией:
- Абстракция позволяет скрыть детали реализации объекта и предоставить только необходимый интерфейс для работы с ним. Это помогает упростить код, сделать его более понятным и гибким.
- Полиморфизм предоставляет возможность использовать один и тот же интерфейс для работы с разными объектами, которые могут иметь различную реализацию. Этот подход значительно упрощает расширение функциональности ПО.
Таким образом, абстракция позволяет определить общий интерфейс для работы с объектами, а полиморфизм позволяет использовать этот интерфейс для работы с различными объектами, которые могут иметь различную реализацию.
Рассмотрим полиморфизм на примере класса Confectionary (кондитерские изделия):
class Confectionary:
def __init__(self, name, price):
self.name = name
self.price = price
def describe(self):
print(f"{self.name} по цене {self.price} руб/кг")
class Cake(Confectionary):
def describe(self):
print(f"{self.name} торт стоит {self.price} руб/кг")
class Candy(Confectionary):
def describe(self):
print(f"{self.name} конфеты стоимостью {self.price} руб/кг")
class Cookie(Confectionary):
pass
В этом примере мы определяем базовый класс Confectionary, который имеет атрибуты name и price, а также метод describe(). Затем мы определяем три подкласса класса Confectionary: Cake, Candy и Cookie. Cake и Candy переопределяют метод describe() своими собственными реализациями, которые включают тип кондитерского изделия (торт и конфеты соответственно), а Cookie наследует дефолтный метод describe() от Confectionary.
Если создать экземпляры этих классов и вызвать их методы describe(), можно убедиться, что результат зависит от реализации метода в каждом конкретном подклассе:
cake = Cake("Пражский", 1200)
candy = Candy("Шоколадные динозавры", 560)
cookie = Cookie("Овсяное печенье с миндалем", 250)
cake.describe()
candy.describe()
cookie.describe()
Вывод:
Пражский торт стоит 1200 руб/кг
Шоколадные динозавры конфеты стоимостью 560 руб/кг
Овсяное печенье с миндалем по цене 250 руб/кг
Теперь разберем на примере класса Beverage (напиток) взаимодействие полиморфизма с другими концепциями ООП. Beverage – родительский класс, который содержит:
- атрибуты названия, объема и цены;
- методы для получения и установки этих атрибутов;
- метод для вывода описания напитка.
Soda (газировка) – дочерний класс Beverage, у него есть дополнительный атрибут flavor (вкус) и собственный метод describe(), включающий flavor. DietSoda – еще один дочерний класс Soda, который наследует все атрибуты и методы Soda, но переопределяет метод describe(), чтобы указать, что газировка является диетической:
class Beverage:
def __init__(self, name, size, price):
self._name = name
self._size = size
self._price = price
def get_name(self):
return self._name
def get_size(self):
return self._size
def get_price(self):
return self._price
def set_price(self, price):
self._price = price
def describe(self):
return f'{self._size} л газировки "{self._name}" стоит {self._price} руб.'
class Soda(Beverage):
def __init__(self, name, size, price, flavor):
super().__init__(name, size, price)
self._flavor = flavor
def get_flavor(self):
return self._flavor
def describe(self):
return f'{self._size} л газировки "{self._name}" со вкусом "{self._flavor}" стоит {self._price} руб.'
class DietSoda(Soda):
def __init__(self, name, size, price, flavor):
super().__init__(name, size, price, flavor)
def describe(self):
return f'{self._size} л диетической газировки "{self._name}" со вкусом "{self._flavor}" стоит {self._price} руб.'
regular_soda = Soda('Sprite', 0.33, 45, 'лимон')
print(regular_soda.describe())
diet_soda = DietSoda('Mirinda', 0.33, 50, 'мандарин')
print(diet_soda.describe())
regular_soda = Soda('Буратино', 1.5, 65, 'дюшес')
print(regular_soda.describe())
Этот пример демонстрирует:
- Инкапсуляцию, поскольку атрибуты защищены символами подчеркивания и могут быть доступны только через методы getter и setter.
- Наследование, поскольку Soda и DietSoda наследуют атрибуты и метод от Beverage.
- Полиморфизм, поскольку каждый класс имеет свою собственную версию метода describe(), который возвращает различные результаты в зависимости от конкретного класса.
Вывод:
0.33 л газировки "Sprite" со вкусом "лимон" стоит 45 руб.
0.33 л диетической газировки "Mirinda" со вкусом "мандарин" стоит 50 руб.
1.5 л газировки "Буратино" со вкусом "дюшес" стоит 65 руб.
Практика
Задание 1
В далекой-далекой галактике Федерация ведет ожесточенную войну с клингонами. Звездолеты Федерации оснащены мощными фазерами, а клингонские корабли – смертоносными фотонными торпедами. Обе стороны разработали усовершенствованные варп-двигатели для перемещения со сверхсветовой скоростью, и оборудовали свои корабли системами самоуничтожения на случай чрезвычайной ситуации.
Для игры, посвященной этой войне, нужно создать абстрактный класс Starship с методами warp_speed(), fire_weapon() и self_destruct(). Кроме того, нужно создать два подкласса FederationStarship и KlingonWarship, которые наследуют абстрактные методы Starship и реализуют свои собственные версии методов warp_speed(), fire_weapon() и self_destruct().
Пример использования:
enterprise = FederationStarship()
bird_of_prey = KlingonWarship()
enterprise.warp_speed()
bird_of_prey.warp_speed()
enterprise.fire_weapon()
bird_of_prey.fire_weapon()
enterprise.self_destruct()
bird_of_prey.self_destruct()
Вывод:
Включить варп-двигатели!
Включить маскировочное устройство!
Выпустить фотонные торпеды!
Стрелять из фазеров!
Запускаю систему самоуничтожения...
Запускаю протокол самоуничтожения...
Решение:
from abc import ABC, abstractmethod
class Starship(ABC):
@abstractmethod
def warp_speed(self):
pass
@abstractmethod
def fire_weapon(self):
pass
@abstractmethod
def self_destruct(self):
pass
class FederationStarship(Starship):
def warp_speed(self):
print("Включить варп-двигатели!")
def fire_weapon(self):
print("Выпустить фотонные торпеды!")
def self_destruct(self):
print("Запускаю систему самоуничтожения...")
class KlingonWarship(Starship):
def warp_speed(self):
print("Включить маскировочное устройство!")
def fire_weapon(self):
print("Стрелять из фазеров!")
def self_destruct(self):
print("Запускаю протокол самоуничтожения...")
Задание 2
Для ПО ресторана нужно разработать модуль, помогающий контролировать использование фруктов и овощей на кухне. Создайте абстрактный класс Ingredient с методами get_name() и get_quantity(). Затем создайте два подкласса Vegetable и Fruit, которые наследуют абстрактные методы от Ingredient и реализуют свои собственные версии методов get_name() и get_quantity().
Пример использования:
carrot = Vegetable("Морковь", 5)
apple = Fruit("Яблоки", 10)
print(carrot.get_name())
print(carrot.get_quantity())
print(apple.get_name())
print(apple.get_quantity())
Вывод:
Морковь
5 кг
Яблоки
10 кг
Решение:
from abc import ABC, abstractmethod
class Ingredient(ABC):
@abstractmethod
def get_name(self):
pass
@abstractmethod
def get_quantity(self):
pass
class Vegetable(Ingredient):
def __init__(self, name, quantity):
self.name = name
self.quantity = quantity
def get_name(self):
return self.name
def get_quantity(self):
return f'{self.quantity} кг'
class Fruit(Ingredient):
def __init__(self, name, quantity):
self.name = name
self.quantity = quantity
def get_name(self):
return self.name
def get_quantity(self):
return f'{self.quantity} кг'
Задание 3
Для военной стратегии необходимо создать абстрактный класс Soldier. Каждый солдат должен уметь двигаться, защищаться и атаковать, поэтому Soldier имеет три абстрактных метода: move(), attack() и defend(). Два конкретных класса, Infantry (пехота) и Cavalry (кавалерия), будут наследовать и реализовывать эти методы. В игре также должен быть класс Army, который будет добавлять солдат в армию и выполнять операции атаки и защиты.
Чтобы гарантировать, что используются только экземпляры
класса Soldier, нужно создать декоратор validatesoldier, который будет
проверять тип объекта. Если объект не является экземпляром класса Soldier,
декоратор выдаст ошибку TypeError
. Декоратор будет применяться к методам
move(), attack() и defend() классов Infantry и Cavalry.
Пример использования:
army = Army()
army.add_soldier(Infantry())
army.add_soldier(Cavalry())
army.add_soldier(Infantry())
army.add_soldier(Cavalry())
army.attack()
army.defend()
Вывод:
Пехота передвигается в пешем порядке
Пехота участвует в ближнем бою
Кавалерия передвигается верхом
Кавалерия переходит в атаку
Пехота передвигается в пешем порядке
Пехота участвует в ближнем бою
Кавалерия передвигается верхом
Кавалерия переходит в атаку
Пехота передвигается в пешем порядке
Пехота держит строй
Кавалерия передвигается верхом
Кавалерия защищает фланги
Пехота передвигается в пешем порядке
Пехота держит строй
Кавалерия передвигается верхом
Кавалерия защищает фланги
Решение:
from abc import ABC, abstractmethod
def validate_soldier(func):
def wrapper(self):
if not isinstance(self, Soldier):
raise TypeError("Объект не является экземпляром Soldier")
return func(self)
return wrapper
class Soldier(ABC):
@abstractmethod
def move(self):
pass
@abstractmethod
def attack(self):
pass
@abstractmethod
def defend(self):
pass
class Infantry(Soldier):
@validate_soldier
def move(self):
print("Пехота передвигается в пешем порядке")
@validate_soldier
def attack(self):
print("Пехота участвует в ближнем бою")
@validate_soldier
def defend(self):
print("Пехота держит строй")
class Cavalry(Soldier):
@validate_soldier
def move(self):
print("Кавалерия передвигается верхом")
@validate_soldier
def attack(self):
print("Кавалерия переходит в атаку")
@validate_soldier
def defend(self):
print("Кавалерия защищает фланги")
class Army:
def __init__(self):
self.soldiers = []
def add_soldier(self, soldier):
self.soldiers.append(soldier)
def attack(self):
for soldier in self.soldiers:
soldier.move()
soldier.attack()
def defend(self):
for soldier in self.soldiers:
soldier.move()
soldier.defend()
Задание 4
Палеонтологам, работающим в заповеднике для динозавров, понадобилось ПО для отслеживания множества травоядных и плотоядных подопечных. Данные, которые нужно учитывать по каждому динозавру – имя, вид, рост, вес и рацион питания.
Создайте абстрактный класс Dinosaur с методами get_personal_name(), get_breed(), get_height(), get_weight() и get_diet(). Затем создайте два подкласса Carnivore (плотоядный) и Herbivore (травоядный), которые наследуют методы Dinosaur и реализуют свои собственные версии get_personal_name(), get_breed(), get_height(), get_weight() и get_diet(). Кроме того, создайте класс DinosaurPark, который содержит список динозавров и имеет методы list_dinosaurs(), list_carnivores() и list_herbivores() для вывода списков a) всех динозавров, b) плотоядных и c) травоядных особей.
Пример использования:
t_rex = Carnivore('Тираннозавр', 'Рекс', 4800, 560)
velociraptor = Carnivore('Велоцираптор', 'Зубастик', 30, 70)
stegosaurus = Herbivore('Стегозавр', 'Стегга', 7100, 420)
triceratops = Herbivore('Трицератопс', 'Трипси', 8000, 290)
park = DinosaurPark()
park.add_dinosaur(t_rex)
park.add_dinosaur(velociraptor)
park.add_dinosaur(stegosaurus)
park.add_dinosaur(triceratops)
for dinosaur in park.list_dinosaurs():
print(f'Имя: {dinosaur[0]}\n'
f'Вид: {dinosaur[1]}\n'
f'Вес: {dinosaur[2]} кг\n'
f'Рост: {dinosaur[3]} см\n'
f'Рацион: {dinosaur[4]}\n')
Вывод:
Имя: Рекс
Вид: Тираннозавр
Вес: 4800 кг
Рост: 560 см
Рацион: Плотоядный
Имя: Зубастик
Вид: Велоцираптор
Вес: 30 кг
Рост: 70 см
Рацион: Плотоядный
Имя: Стегга
Вид: Стегозавр
Вес: 7100 кг
Рост: 420 см
Рацион: Травоядный
Имя: Трипси
Вид: Трицератопс
Вес: 8000 кг
Рост: 290 см
Рацион: Травоядный
Решение:
from abc import ABC, abstractmethod
class Dinosaur(ABC):
@abstractmethod
def get_personal_name(self):
pass
@abstractmethod
def get_breed(self):
pass
@abstractmethod
def get_height(self):
pass
@abstractmethod
def get_weight(self):
pass
@abstractmethod
def get_diet(self):
pass
class Carnivore(Dinosaur):
def __init__(self, breed, personal_name, height, weight):
self.breed = breed
self.personal_name = personal_name
self.height = height
self.weight = weight
def get_personal_name(self):
return self.personal_name
def get_breed(self):
return self.breed
def get_height(self):
return self.height
def get_weight(self):
return self.weight
def get_diet(self):
return 'Плотоядный'
class Herbivore(Dinosaur):
def __init__(self, breed, personal_name, height, weight):
self.breed = breed
self.personal_name = personal_name
self.height = height
self.weight = weight
def get_personal_name(self):
return self.personal_name
def get_breed(self):
return self.breed
def get_height(self):
return self.height
def get_weight(self):
return self.weight
def get_diet(self):
return 'Травоядный'
class DinosaurPark:
def __init__(self):
self.dinosaurs = []
def add_dinosaur(self, dinosaur):
self.dinosaurs.append(dinosaur)
def list_dinosaurs(self):
return [(dinosaur.get_personal_name(), dinosaur.get_breed(), dinosaur.get_height(), dinosaur.get_weight(), dinosaur.get_diet()) for dinosaur in self.dinosaurs]
def list_carnivores(self):
return [(dinosaur.get_personal_name(), dinosaur.get_breed(), dinosaur.get_height(), dinosaur.get_weight()) for dinosaur in self.dinosaurs if isinstance(dinosaur, Carnivore)]
def list_herbivores(self):
return [(dinosaur.get_personal_name(), dinosaur.get_breed(), dinosaur.get_height(), dinosaur.get_weight()) for dinosaur in self.dinosaurs if isinstance(dinosaur, Herbivore)]
Задание 5
Для учета музыкальных инструментов в оркестре нужно создать абстрактный класс Instrument с методами get_name(), get_type(), get_sound() и play(). Два подкласса StringedInstrument (струнные) и PercussionInstrument (ударные) наследуют методы Instrument и реализуют свои собственные версии методов get_name(), get_type(), get_sound() и play(). Кроме того, необходимо реализовать класс Orchestra: он добавляет новые инструменты и имеет методы list_instruments(), list_stringed_instruments(), list_percussion_instruments(), которые выводят списки a) всех инструментов, b) ударных, c) струнных.
Пример использования:
chello = StringedInstrument("виолончель", "струнный инструмент", "Strum")
maracas = PercussionInstrument("маракасы", "ударный инструмент", "Maracas")
violin = StringedInstrument("скрипка", "струнный инструмент", "Virtuso")
drums = PercussionInstrument("барабан", "ударный инструмент", "Beat")
orchestra = Orchestra()
orchestra.add_instrument(chello)
orchestra.add_instrument(maracas)
orchestra.add_instrument(violin)
orchestra.add_instrument(drums)
print("В оркестрe есть инструменты:", ', '.join(orchestra.list_instruments()))
print("Струнные инструменты:", ', '.join(orchestra.list_stringed_instruments()))
print("Ударные инструменты:", ', '.join(orchestra.list_percussion_instruments()))
print(chello.play())
print(drums.play())
Вывод:
В оркестрe есть инструменты: виолончель, маракасы, скрипка, барабан
Струнные инструменты: виолончель, скрипка
Ударные инструменты: маракасы, барабан
Звучит струнный инструмент виолончель
Звучит ударный инструмент барабан
Решение:
from abc import ABC, abstractmethod
def print_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result
return wrapper
class Instrument(ABC):
@abstractmethod
def get_name(self):
pass
@abstractmethod
def get_type(self):
pass
@abstractmethod
def get_sound(self):
pass
@abstractmethod
def play(self):
pass
class StringedInstrument(Instrument):
def __init__(self, name, type, sound):
self.name = name
self.type = type
self.sound = sound
def get_name(self):
return self.name
def get_type(self):
return self.type
def get_sound(self):
return self.sound
def play(self):
return f"Звучит {self.type} {self.name}"
class PercussionInstrument(Instrument):
def __init__(self, name, type, sound):
self.name = name
self.type = type
self.sound = sound
def get_name(self):
return self.name
def get_type(self):
return self.type
def get_sound(self):
return self.sound
def play(self):
return f"Звучит {self.type} {self.name}"
class Orchestra:
def __init__(self):
self.instruments = []
def add_instrument(self, instrument):
self.instruments.append(instrument)
@print_decorator
def list_instruments(self):
return [instrument.get_name() for instrument in self.instruments]
@print_decorator
def list_stringed_instruments(self):
return [instrument.get_name() for instrument in self.instruments if isinstance(instrument, StringedInstrument)]
@print_decorator
def list_percussion_instruments(self):
return [instrument.get_name() for instrument in self.instruments if isinstance(instrument, PercussionInstrument)]
Задание 6
Напишите класс FilmCatalogue (каталог фильмов), который отвечает за ведение фильмотеки. FilmCatalogue должен поддерживать различные типы кинокартин, чтобы пользователи могли искать фильмы по определенному жанру. Для этого необходимо создать новые классы для различных жанров (например, Horror, Action, Romance), которые наследуют класс Movie и переопределяют метод play() для вывода информации о том, к какому жанру относится фильм.
Пример использования:
my_catalogue = FilmCatalogue()
my_catalogue.add_movie(Drama("Крестный отец", "Френсис Ф. Коппола"))
my_catalogue.add_movie(Comedy("Ночные игры", "Джон Фрэнсис Дейли, Джонатан М. Голдштейн"))
my_catalogue.add_movie(Horror("Дракула Брэма Стокера", "Френсис Ф. Коппола"))
my_catalogue.add_movie(Action("Крушение", "Жан-Франсуа Рише"))
my_catalogue.add_movie(Romance("Честная куртизанка", "Маршалл Херсковиц"))
my_catalogue.play_all_movies()
print(f"\nНайдены фильмы ужасов:")
for movie in my_catalogue.search_movies_by_genre(Horror):
print(movie.title)
print(f"\nЗапускаем фильм из жанра 'Мелодрамы':")
my_catalogue.play_movies_by_genre(Romance)
Вывод:
Включаем драму 'Крестный отец' реж. Френсис Ф. Коппола.
Включаем комедию 'Ночные игры' реж. Джон Фрэнсис Дейли, Джонатан М. Голдштейн.
Включаем фильм ужасов 'Дракула Брэма Стокера' реж. Френсис Ф. Коппола.
Включаем боевик 'Крушение' реж. Жан-Франсуа Рише.
Включаем мелодраму 'Честная куртизанка' реж. Маршалл Херсковиц.
Найдены фильмы ужасов:
Дракула Брэма Стокера
Запускаем фильм из жанра 'Мелодрамы':
Включаем мелодраму 'Честная куртизанка' реж. Маршалл Херсковиц.
Решение:
class Movie:
def __init__(self, title, director):
self.title = title
self.director = director
def play(self):
print(f"Собираемся смотреть фильм '{self.title}' реж. {self.director}.")
class Comedy(Movie):
def play(self):
print(f"Включаем комедию '{self.title}' реж. {self.director}.")
class Drama(Movie):
def play(self):
print(f"Включаем драму '{self.title}' реж. {self.director}.")
class Horror(Movie):
def play(self):
print(f"Включаем фильм ужасов '{self.title}' реж. {self.director}.")
class Action(Movie):
def play(self):
print(f"Включаем боевик '{self.title}' реж. {self.director}.")
class Romance(Movie):
def play(self):
print(f"Включаем мелодраму '{self.title}' реж. {self.director}.")
class FilmCatalogue:
def __init__(self):
self.movies = []
def add_movie(self, movie):
self.movies.append(movie)
def play_all_movies(self):
for movie in self.movies:
movie.play()
def search_movies_by_genre(self, genre):
return [movie for movie in self.movies if isinstance(movie, genre)]
def play_movies_by_genre(self, genre):
movies = self.search_movies_by_genre(genre)
for movie in movies:
movie.play()
Задание 7
Для CRM винодельни нужно написать модуль, отвечающий за учет красных, белых и розовых вин, каждое из которых имеет свое название, сорт винограда, год и температуру подачи. Создайте базовый класс Wine с атрибутами name, grape и year. Затем создайте три подкласса RedWine, WhiteWine и RoseWine, которые наследуют методы и атрибуты от Wine и реализуют свои собственные версии метода serve(). Кроме того, создайте класс Winery, который ведет список вин и имеет метод serve_wines(), вызывающий метод serve() для каждого вина.
Пример использования:
winery = Winery()
winery.add_wine(RedWine("Cabernet Sauvignon", "Каберне Совиньон", 2015))
winery.add_wine(WhiteWine("Chardonnay", "Шардоне", 2018))
winery.add_wine(RoseWine("Grenache", "Гренаш", 2020))
winery.serve_wines()
Вывод:
Красное вино 'Cabernet Sauvignon', сделанное из винограда сорта Каберне Совиньон в 2015 году, рекомендуем подавать комнатной температуры.
Белое вино 'Chardonnay', сделанное из винограда сорта Шардоне в 2018 году, рекомендуем подавать хорошо охлажденным.
Розовое вино 'Grenache', сделанное из винограда сорта Гренаш в 2020 году, рекомендуем подавать слегка охлажденным.
Решение:
class Wine:
def __init__(self, name, grape, year):
self.name = name
self.grape = grape
self.year = year
class RedWine(Wine):
def serve(self):
print(f"Красное вино '{self.name}', сделанное из винограда сорта {self.grape} в {self.year} году, рекомендуем подавать комнатной температуры.")
class WhiteWine(Wine):
def serve(self):
print(f"Белое вино '{self.name}', сделанное из винограда сорта {self.grape} в {self.year} году, рекомендуем подавать хорошо охлажденным.")
class RoseWine(Wine):
def serve(self):
print(f"Розовое вино '{self.name}', сделанное из винограда сорта {self.grape} в {self.year} году, рекомендуем подавать слегка охлажденным.")
class Winery:
def __init__(self):
self.wines = []
def add_wine(self, wine):
self.wines.append(wine)
def serve_wines(self):
for wine in self.wines:
wine.serve()
Задание 8
Для ПО аэропорта нужно разработать модуль, отслеживающий пассажирские и грузовые самолеты, которые отличаются моделью, производителем, вместимостью и грузоподъемностью. Создайте базовый класс Aircraft (воздушное судно) с атрибутами model, manufacturer и capacity. Затем создайте два подкласса PassengerAircraft и CargoAircraft, которые наследуют атрибуты и методы от Aircraft и реализуют свои собственные версии метода fly(). В дополнение создайте класс Airport, который содержит список самолетов и имеет метод takeoff(), вызывающий метод fly() для каждого самолета.
Пример использования:
airport = Airport()
airport.add_aircraft(PassengerAircraft("Boeing 747", "Боинг", 416))
airport.add_aircraft(CargoAircraft("Airbus A330", "Эйрбас", 70))
airport.add_aircraft(PassengerAircraft("Boeing 777", "Боинг", 396))
airport.takeoff()
Вывод:
Пассажирский самолет 'Boeing 747' вместимостью 416 человек, произведенный компанией Боинг, поднимается в воздух с пассажирами на борту.
Грузовой самолет 'Airbus A330' с грузоподъемностью 70 т, произведенный компанией Эйрбас, поднимается в воздух с грузом на борту.
Пассажирский самолет 'Boeing 777' вместимостью 396 человек, произведенный компанией Боинг, поднимается в воздух с пассажирами на борту.
Решение:
class Aircraft:
def __init__(self, model, manufacturer, capacity):
self.model = model
self.manufacturer = manufacturer
self.capacity = capacity
class PassengerAircraft(Aircraft):
def fly(self):
print(f"Пассажирский самолет '{self.model}' вместимостью {self.capacity} человек, произведенный компанией {self.manufacturer}, поднимается в воздух с пассажирами на борту.")
class CargoAircraft(Aircraft):
def fly(self):
print(f"Грузовой самолет '{self.model}' с грузоподъемностью {self.capacity} т, произведенный компанией {self.manufacturer}, поднимается в воздух с грузом на борту.")
class Airport:
def __init__(self):
self.aircrafts = []
def add_aircraft(self, aircraft):
self.aircrafts.append(aircraft)
def takeoff(self):
for aircraft in self.aircrafts:
aircraft.fly()
Задание 9
Необходимо реализовать модуль, отвечающий за обработку данных о тестировании конфигурации настольных компьютеров и ноутбуков, каждый из которых отличается моделью, процессором, памятью и производительностью. Создайте базовый класс Computer с атрибутами model, processor и memory. Затем создайте два подкласса Desktop и Laptop, которые наследуют атрибуты и методы Computer и реализуют свои собственные версии метода run(). В дополнение, создайте класс ComputerStore, который содержит список компьютеров и имеет метод run_tests(), вызывающий метод run() для каждого компьютера. Используйте декораторы для вывода результатов.
Пример использования:
store = ComputerStore()
store.add_computer(Desktop("HP Legion", "Intel Core i9-10900K", "64 Гб"))
store.add_computer(Laptop("Dell Xtra", "Intel Core i5 13600K", "32 Гб"))
store.add_computer(Desktop("Lenovo SuperPad", "AMD Ryzen 7 2700X", "16 Гб"))
store.run_tests()
Вывод:
Начинаем тест производительности...
Запускаем настольный компьютер 'HP Legion' с процессором Intel Core i9-10900K и 64 Гб RAM.
Тест производительности завершен.
Начинаем тест производительности...
Запускаем ноутбук 'Dell Xtra' с процессором Intel Core i5 13600K и 32 Гб RAM.
Тест производительности завершен.
Начинаем тест производительности...
Запускаем настольный компьютер 'Lenovo SuperPad' с процессором AMD Ryzen 7 2700X и 16 Гб RAM.
Тест производительности завершен.
Решение:
def performance_test(func):
def wrapper(*args, **kwargs):
print("Начинаем тест производительности...")
result = func(*args, **kwargs)
print("Тест производительности завершен.")
return result
return wrapper
class Computer:
def __init__(self, model, processor, memory):
self.model = model
self.processor = processor
self.memory = memory
@performance_test
def run(self):
pass
class Desktop(Computer):
@performance_test
def run(self):
print(f"Запускаем настольный компьютер '{self.model}' с процессором {self.processor} и {self.memory} RAM.")
class Laptop(Computer):
@performance_test
def run(self):
print(f"Запускаем ноутбук '{self.model}' с процессором {self.processor} и {self.memory} RAM.")
class ComputerStore:
def __init__(self):
self.computers = []
def add_computer(self, computer):
self.computers.append(computer)
def run_tests(self):
for computer in self.computers:
computer.run()
Задание 10
Определите базовый класс Cryptocurrency, имеющий атрибуты:
- name – название;
- symbol – символ-тикер;
- minable – возможность добычи майнингом;
- rate_to_usd – текущий курс к доллару;
- anonymous – наличие анонимных транзакций.
Затем создайте три подкласса Nano, Iota и Stellar, которые наследуют атрибуты и методы родительского класса Cryptocurrency, и обладают дополнительными свойствами, влияющими на размер вознаграждения за майнинг:
- атрибут block_lattice у Nano;
- tangle у Iota.
Кроме того, нужно реализовать:
- Декоратор minable_required, который проверяет, можно ли майнить криптовалюту перед вызовом метода mining_reward(), и выводит сообщение, если ее майнить нельзя.
- Функцию print_info, которая принимает на вход экземпляр криптовалюты и выводит информацию о монете, включая название, символ, возможность добычи, курс к доллару США, анонимность и наличие блок-решетки.
Пример использования:
cryptocurrencies = [Nano(block_lattice=True, rate_to_usd=6, anonymous=False),
Iota(tangle=True, rate_to_usd=0.4, anonymous=False),
Stellar(distributed=False, rate_to_usd=0.15, anonymous=True)]
for crypto in cryptocurrencies:
print_info(crypto)
if crypto.minable:
print(f"Награда за майнинг: {crypto.mining_reward()} {crypto.symbol}\n")
Вывод:
Nano (NANO): добывают майнингом, курс к USD: 6, только публичные транзакции, блок-решетка
Награда за майнинг: 0.02 NANO
Iota (IOTA): добывают майнингом, курс к USD: 0.4, только публичные транзакции
Награда за майнинг: 0.001 IOTA
Stellar (XLM): не майнится, курс к USD: 0.15, анонимные транзакции
Решение:
class Cryptocurrency:
def __init__(self, name, symbol, minable=True, rate_to_usd=1, anonymous=False):
self.name = name
self.symbol = symbol
self.minable = minable
self.rate_to_usd = rate_to_usd
self.anonymous = anonymous
def __str__(self):
return f"{self.name} ({self.symbol})"
def mining_reward(self):
return None
def minable_required(func):
def wrapper(crypto):
if not crypto.minable:
print("Эту криптовалюту не майнят")
return None
return func(crypto)
return wrapper
class Nano(Cryptocurrency):
def __init__(self, block_lattice=False, rate_to_usd=5, anonymous=True):
super().__init__("Nano", "NANO", True, rate_to_usd, anonymous)
self.block_lattice = block_lattice
@minable_required
def mining_reward(self):
return 0.02 if self.block_lattice else 0.01
class Iota(Cryptocurrency):
def __init__(self, tangle=False, rate_to_usd=0.3, anonymous=False):
super().__init__("Iota", "IOTA", True, rate_to_usd, anonymous)
self.tangle = tangle
@minable_required
def mining_reward(self):
return 0.001 if self.tangle else 0.002
class Stellar(Cryptocurrency):
def __init__(self, distributed=False, rate_to_usd=0.1, anonymous=True):
super().__init__("Stellar", "XLM", False, rate_to_usd, anonymous)
self.distributed = distributed
def mining_reward(self):
print("Stellar is not minable")
return None
def print_info(crypto):
minable_str = 'добывают майнингом' if crypto.minable else 'не майнится'
anonymity_str = 'анонимные транзакции' if crypto.anonymous else 'только публичные транзакции'
block_lattice_str = 'блок-решетка' if hasattr(crypto, 'block_lattice') and crypto.block_lattice else ''
if block_lattice_str:
print(f"{crypto}: {minable_str}, курс к USD: {crypto.rate_to_usd}, {anonymity_str}, {block_lattice_str}")
else:
print(f"{crypto}: {minable_str}, курс к USD: {crypto.rate_to_usd}, {anonymity_str}")
Подведем итоги
Объектно-ориентированный подход обладает несколькими важными преимуществами, среди которых:
- Модульность – ООП позволяет разбивать сложные задачи на более мелкие и простые составляющие.
- Возможность переиспользования – можно многократно использовать однажды написанный код с помощью наследования и полиморфизма.
- Инкапсуляция – можно скрыть детали реализации одних модулей от других частей программы. Это помогает сделать код более безопасным, поскольку другие части вашей программы не могут получить доступ или изменить данные или методы, которые не предназначены для использования извне.
- Гибкость – ООП позволяет легко создавать новые объекты и изменять существующие. Можно добавлять или удалять методы и свойства по мере необходимости, а также создавать новые объекты, которые наследуют атрибуты и методы существующих классов.
- Простота сопровождения и отладки. Разделение код на составляющие модули упрощает структуру программы и помогает изолировать ошибки. Кроме того, использование описательных имен классов и методов делает код более читабельным и понятным.
В следующей части будем изучать основы разработки приложений с графическим интерфейсом.
Содержание самоучителя
- Особенности, сферы применения, установка, онлайн IDE
- Все, что нужно для изучения Python с нуля – книги, сайты, каналы и курсы
- Типы данных: преобразование и базовые операции
- Методы работы со строками
- Методы работы со списками и списковыми включениями
- Методы работы со словарями и генераторами словарей
- Методы работы с кортежами
- Методы работы со множествами
- Особенности цикла for
- Условный цикл while
- Функции с позиционными и именованными аргументами
- Анонимные функции
- Рекурсивные функции
- Функции высшего порядка, замыкания и декораторы
- Методы работы с файлами и файловой системой
- Регулярные выражения
- Основы скрапинга и парсинга
- Основы ООП: инкапсуляция и наследование
- Основы ООП: абстракция и полиморфизм
- Графический интерфейс на Tkinter
- Основы разработки игр на Pygame
- Основы работы с SQLite
- Основы веб-разработки на Flask
- Основы работы с NumPy
- Основы анализа данных с Pandas
Комментарии