diff --git a/mod_oop/3.1_7_course_model.py b/mod_oop/3.1_7_course_model.py new file mode 100644 index 0000000..11c59e5 --- /dev/null +++ b/mod_oop/3.1_7_course_model.py @@ -0,0 +1,281 @@ +""" + https://stepik.org/lesson/701986/step/7?unit=702087 + + Необходимо создать программу для обучающего курса. Для этого объявляются три класса: + +Course - класс, отвечающий за управление курсом в целом; +Module - класс, описывающий один модуль (раздел) курса; +LessonItem - класс одного занятия (урока). + +Объекты класса LessonItem должны создаваться командой: +lesson = LessonItem(название урока, число практических занятий, общая длительность урока) +>>> lesson = LessonItem("название урока", 2, 10) + +Соответственно, в каждом объекте класса LessonItem должны создаваться локальные атрибуты: +title - название урока (строка); +practices - число практических занятий (целое положительное число); +duration - общая длительность урока (целое положительное число). + +Необходимо с помощью магических методов реализовать следующую логику взаимодействия с объектами класса LessonItem: +1. Проверять тип присваиваемых данных локальным атрибутам. Если типы не соответствуют требованиям, то генерировать исключение командой: +raise TypeError("Неверный тип присваиваемых данных.") +2. При обращении к несуществующим атрибутам объектов класса LessonItem возвращать значение False. +3. Запретить удаление атрибутов title, practices и duration в объектах класса LessonItem. + + --- тесты типов --- +>>> LessonItem(1, 2, 10) +Traceback (most recent call last): + ... +TypeError: Неверный тип присваиваемых данных. +>>> LessonItem("название урока", -2, 10) +Traceback (most recent call last): + ... +TypeError: Неверный тип присваиваемых данных. +>>> LessonItem("название урока", 2, -10) +Traceback (most recent call last): + ... +TypeError: Неверный тип присваиваемых данных. + +------------------- + +Объекты класса Module должны создаваться командой: +>>> module = Module("название модуля") + +Каждый объект класса Module должен содержать локальные атрибуты: +name - название модуля; +lessons - список из уроков (объектов класса LessonItem), входящих в модуль (изначально список пуст). + +Также в классе Module должны быть реализованы методы: +add_lesson(self, lesson) - добавление в модуль (в конец списка lessons) нового урока (объекта класса LessonItem); +remove_lesson(self, indx) - удаление урока по индексу в списке lessons. + +Наконец, объекты класса Course создаются командой: +course = Course(название курса) + +И содержат следующие локальные атрибуты: +name - название курса (строка); +modules - список модулей в курсе (изначально список пуст). + +Также в классе Course должны присутствовать следующие методы: +add_module(self, module) - добавление нового модуля в конце списка modules; +remove_module(self, indx) - удаление модуля из списка modules по индексу в этом списке. + +Пример использования классов (в программе эти строчки не писать): + +course = Course("Python ООП") +module_1 = Module("Часть первая") +module_1.add_lesson(LessonItem("Урок 1", 7, 1000)) +module_1.add_lesson(LessonItem("Урок 2", 10, 1200)) +module_1.add_lesson(LessonItem("Урок 3", 5, 800)) +course.add_module(module_1) +module_2 = Module("Часть вторая") +module_2.add_lesson(LessonItem("Урок 1", 7, 1000)) +module_2.add_lesson(LessonItem("Урок 2", 10, 1200)) +course.add_module(module_2) +P.S. На экран ничего выводить не нужно. + +>>> def make_course(): +... course = Course("Python ООП") +... module_1 = Module("Часть первая") +... module_1.add_lesson(LessonItem("Урок 1", 7, 1000)) +... module_1.add_lesson(LessonItem("Урок 2", 10, 1200)) +... module_1.add_lesson(LessonItem("Урок 3", 5, 800)) +... course.add_module(module_1) +... module_2 = Module("Часть вторая") +... module_2.add_lesson(LessonItem("Урок 1", 7, 1000)) +... module_2.add_lesson(LessonItem("Урок 2", 10, 1200)) +... course.add_module(module_2) +... return course +>>> ((course := make_course()).name, { +... module.name: [ +... f"{lesson.title}, {lesson.practices}, {lesson.duration}" +... for lesson in module.lessons +... ] +... for module in course.modules +... }) +('Python ООП', {'Часть первая': ['Урок 1, 7, 1000', 'Урок 2, 10, 1200', 'Урок 3, 5, 800'], 'Часть вторая': ['Урок 1, 7, 1000', 'Урок 2, 10, 1200']}) +""" + + +class LessonItem: + title: str + practices: int + duration: int + + class Validators: + @staticmethod + def is_positive(value) -> bool: + return value > 0 + + @staticmethod + def olways_ok(_) -> bool: + return True + + __nodel = "title", "practices", "duration" + __validators = { + "practices": Validators.is_positive, + "duration": Validators.is_positive, + } + + def __init__(self, title: str, practices: int, duration: int): + self.title, self.practices, self.duration = title, practices, duration + + def __getattr__(self, name: str): + return False + + def __setattr__(self, name: str, value): + if not ( + isinstance(value, self.__annotations__.get(name, object)) + and self.__validators.get(name, self.Validators.olways_ok)(value) + ): + raise TypeError("Неверный тип присваиваемых данных.") + super().__setattr__(name, value) + + def __delattr__(self, name: str): + if name in self.__nodel: + raise AttributeError(f"Нельзя удалить атрибут {name}") + + +class Module: + def __init__(self, name: str): + self.name, self.lessons = name, [] + + def add_lesson(self, lesson: LessonItem): + self.lessons.append(lesson) + + def remove_lesson(self, indx: int): + del self.lessons[indx] + + +class Course: + def __init__(self, name: str): + self.name, self.modules = name, [] + + def add_module(self, module: Module): + self.modules.append(module) + + def remove_module(self, indx: int): + del self.modules[indx] + + +def tests(): + # из коммента https://stepik.org/lesson/701986/step/7?discussion=7381286&unit=702087 + # TEST-TASK___________________________________ + course = Course("Python ООП") + assert type(course.name) is str, "название курса должно быть строкой" + assert ( + type(course.modules) is list and len(course.modules) == 0 + ), "modules должен быть списком, изначально список пуст" + # add_module(self, module) - добавление нового модуля в конце списка modules; + # remove_module(self, indx) - удаление модуля из списка modules по индексу в этом списке. + assert hasattr(course, "add_module"), "add_module необъявлен" + assert hasattr(course, "remove_module"), "remove_module необъявлен" + + # + module_1 = Module("Часть первая") + module_2 = Module("Часть вторая") + assert type(module_1.name) is str, "название модуля должно быть строкой" + assert ( + type(module_1.lessons) is list and len(module_1.lessons) == 0 + ), "lesson должен быть списком, изначально список пуст" + # add_lesson(self, lesson) - добавление в модуль (в конец списка lessons) нового урока (объекта класса LessonItem); + # remove_lesson(self, indx) - удаление урока по индексу в списке lessons. + assert hasattr(module_1, "add_lesson"), "add_lesson необъявлен" + assert hasattr(module_1, "remove_lesson"), "remove_lesson необъявлен" + + # + les_1 = LessonItem("Урок 1", 7, 1000) + les_2 = LessonItem("Урок 2", 10, 1200) + assert type(les_1.title) is str, "название урока должно быть строкой" + assert ( + type(les_1.practices) is int and les_1.practices > 0 + ), "practices должен быть целым числом больше ноля" + assert ( + type(les_1.duration) is int and les_1.practices > 0 + ), "duration должен быть целым положительным числом" + + # + # проверка методов + course.add_module(module_1) + course.add_module(module_2) + assert ( + len(course.modules) == 2 and course.modules[1] == module_2 + ), "некоректно отработал метод add_module" + course.remove_module(0) + assert ( + module_1 not in course.modules and len(course.modules) == 1 + ), "некоректно отработал метод remove_module" + # + module_1.add_lesson(les_1) + module_1.add_lesson(les_2) + assert ( + len(module_1.lessons) == 2 and module_1.lessons[1] == les_2 + ), "некоректно отработал метод add_lesson" + module_1.remove_lesson(0) + assert ( + les_1 not in module_1.lessons and len(module_1.lessons) == 1 + ), "некоректно отработал метод remove_lesson" + # + # проверка методов - LessonItem + # 1. Проверять тип присваиваемых данных локальным атрибутам. Если типы не соответствуют требованиям, то генерировать исключение командой: + # raise TypeError("Неверный тип присваиваемых данных.") + try: + les_3 = LessonItem(3, 8, 900) + except TypeError: + assert True + else: + assert ( + False + ), "не сгенерировалось исключение TypeError при записи некорректных данных в title" + + try: + les_3 = LessonItem("Урок 2", 8.0, 900) + except TypeError: + assert True + else: + assert ( + False + ), "не сгенерировалось исключение TypeError при записи некорректных данных в practices" + + try: + les_3 = LessonItem("Урок 2", 8, 900, 0) + except TypeError: + assert True + else: + assert ( + False + ), "не сгенерировалось исключение TypeError при записи некорректных данных в duration" + + # 2. При обращении к несуществующим атрибутам объектов класса LessonItem возвращать значение False. + les_4 = LessonItem("Урок 2", 8, 900) + assert hasattr( + les_1, "__getattr__" + ), "ошибка при обращении к несуществующему локальному атрибуту, метод должен вернуть False" + assert ( + les_4.value_not_atr is False + ), "ошибка при обращении к несуществующему локальному атрибуту, метод должен вернуть False" + # 3. Запретить удаление атрибутов title, practices и duration в объектах класса LessonItem. + assert hasattr( + les_4, "__delattr__" + ), "возможно вы не продумали запрет на удаление локальных атрибутов - title, practices и duration" + try: + del les_4.title + del les_4.practices + del les_4.duration + except: + ... + else: + if any( + True if _ not in les_4.__dict__ else False + for _ in ["title", "practices", "duration"] + ): + print( + "Ошибка при удалении локальных атрибутов - title, practices и duration" + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + tests()