Логирование в формате

Что нужно сделать

Реализуйте декоратор, который будет логировать все методы декорируемого класса (кроме магических методов) и в который можно передавать формат вывода даты и времени логирования.

Пример кода, передаётся формат «Месяц День Год - Часы Минуты Секунды»:

@log_methods("b d Y - H:M:S")
class A:
    def test_sum_1(self) -> int:
        print('test sum 1')
        number = 100
        result = 0
        for _ in range(number + 1):
            result += sum([i_num ** 2 for i_num in range(10000)])

        return result


@log_methods("b d Y - H:M:S")
class B(A):
    def test_sum_1(self):
        super().test_sum_1()
        print("Наследник test sum 1")

    def test_sum_2(self):
        print("test sum 2")
        number = 200
        result = 0
        for _ in range(number + 1):
            result += sum([i_num ** 2 for i_num in range(10000)])

        return result


my_obj = B()
my_obj.test_sum_1()
my_obj.test_sum_2()

Результат:

- Запускается 'B.test_sum_1'. Дата и время запуска: Apr 23 2021 - 21:50:37 
- Запускается 'A.test_sum_1'. Дата и время запуска: Apr 23 2021 - 21:50:37 
Тут метод test_sum_1
- Завершение 'A.test_sum_1', время работы = 0.187s 
Тут метод test_sum_1 у наследника
- Завершение 'B.test_sum_1', время работы = 0.187s 
- Запускается 'B.test_sum_2'. Дата и время запуска: Apr 23 2021 - 21:50:37 
Тут метод test_sum_2 у наследника
- Завершение 'B.test_sum_2', время работы = 0.370s

Совет: внимательно пересмотрите видео 29.4, если сталкиваетесь с трудностями при решении этой задачи.

Что оценивается

  • Результат вычислений корректен.
  • Модели реализованы в стиле ООП, основной функционал описан в методах классов и в отдельных функциях.
  • При написании классов соблюдаются основные принципы ООП: инкапсуляция, наследование и полиморфизм.
    • Для получения и установки значений у приватных атрибутов используются сеттеры и геттеры с соответствующими декораторами.
    • Для создания нового класса на основе уже существующего используется наследование.
    • Для статических и классовых методов используется декоратор classmethod.
  • Формат вывода соответствует примеру.
  • Переменные, функции и собственные методы классов имеют значащие имена, не abcd.
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращают, то используется None.
from datetime import datetime
import time


def logged(cls, func, fmt_str):
  """Декоратор для логирования вызываемого метода класса."""

  def inner(*args, **kwargs):
    print(f'Запускается "{cls.__name__}.{func.__name__}". '
          f'Дата и время запуска: {datetime.now().strftime(fmt_str)}')
    start = time.time()
    result = func(*args, **kwargs)
    end = time.time() - start
    print(f'Завершение "{cls.__name__}.{func.__name__}", '
          f'время работы = {round(end, 3)}s')
    return result
  return inner

def log_methods(fmt_str: str):
  """Декоратор для логирования класса."""
  def decorator(cls):
    for i_method in dir(cls):
      if i_method.startswith('__'):
        continue
      value = getattr(cls, i_method)
      if hasattr(value, '__call__'):
        decorated_a = logged(cls, value, fmt_str)
        setattr(cls, i_method, decorated_a)
    return cls
  return decorator