Класс удобства таймера для таймера выполнения кода для вывода пользователя

Я реализовал удобный класс, чтобы рассчитать время выполнения кода и распечатать его для пользователя. Это часть более крупного проекта, который будет распространяться среди внешних пользователей. Однако таймер вызывается только другими процедурами, а не пользователем напрямую. Мне было интересно реализовать это сам, поэтому я не ищу внешний модуль или аналогичный. Строки документации здесь опущены, функции довольно просты.

from time import perf_counter


class Timer:
    def __init__(self, default_round_digits: int = 5):
        self.default_digits = default_round_digits
        self.start_timer()

    def start_timer(self) -> None:
        self.start_time = perf_counter()
        self.pause_start_time = None
        self.pause_length = 0

    def pause_timer(self) -> None:
        self.pause_start_time = perf_counter()

    def resume_timer(self) -> None:
        if self.pause_start_time is not None:
            self.pause_length += perf_counter() - self.pause_start_time
            self.pause_start_time = None

    @property
    def elapsed_seconds(self) -> float:
        # If timer is paused only consider time up to self.pause_start_time instead of now
        if self.pause_start_time is not None:
            return self.pause_start_time - self.start_time - self.pause_length
        else:
            return perf_counter() - self.start_time - self.pause_length

    def get_timer(self, round_digits: int = None, print_text: str = None, restart: bool = False) -> float:

        if round_digits is None:
            round_digits = self.default_digits

        elapsed_seconds = round(self.elapsed_seconds, ndigits=round_digits)

        if print_text is not None:
            print(f"{print_text}{elapsed_seconds} seconds")

        if restart:
            self.start_timer()

        return elapsed_seconds

    def __str__(self) -> str:
        state = "Running" if self.pause_start_time is None else "Paused"
        return f"{state} Timer at {self.get_timer()} seconds"

Я все еще не уверен в инициализации атрибутов вне __init__. Я знаю, что это не рекомендуется, но копирование start_timer в __init__ мне показался худшим вариантом.

2 ответа
2

  1. Ваш Timer класс содержит два таймера.

    1. Таймер всей продолжительности.
    2. Таймер для отслеживания пауз.

    Я бы вынес код основного таймера в отдельный класс. И мы видим, что вы начинаете повторяться.

  2. Инициализация таймера не должна запускать таймер. Должен ли я сразу начать работать, если я открою приложение таймера?

    Если я хочу создать несколько таймеров, но не хочу, чтобы таймер запускался, ваш класс не дает мне никаких вариантов. Однако просто не звоню start_timer в __init__ позволит каждому использовать ваш класс, как бы то ни было.

    Если вы действительно хотите запустить таймер при построении объекта, вы можете написать метод класса.

    class Timer:
        @classmethod
        def run(cls, *args, **kwargs):
            self = cls(*args, **kwargs)
            self.start_timer()
            return self
    
    # option 1
    timer = Timer()
    timer.start_timer()
    
    # option 2
    timer = Timer.run()
    
  3. start_timer перезапускает таймеры. Вам следует переименовать метод или заставить его работать так, как ожидается.

  4. Если вы позвоните pause_timer дважды без звонка resume_timer ваш код действует так, как будто таймер никогда не ставился на паузу в первый раз.

  5. Я действительно не вижу смысла делать elapsed_seconds недвижимость.

  6. get_timer делает слишком много и нарушает принцип SRP.

    1. Округляет истекшее время до указанного количества десятичных знаков.
    2. Печатает истекшее время.
    3. Перезапускает таймер.
    4. Возвращает истекшее время.

    Вы должны создать функцию для округления прошедшего времени. Затем оставьте печать и перезапуск для пользователя.

  7. Я бы хотел еще пару методов из функциональности, которую я ожидал бы от таймера.

from time import perf_counter


class CoreTimer:
    def __init__(self):
        self.stop()

    def __bool__(self):
        return bool(self.start_time)

    def start(self):
        self.start_time = perf_counter()

    def stop(self):
        self.start_time = 0

    def time_taken(self, now):
        return now - self.start_time

    def elapsed(self):
        return self.time_taken(perf_counter())


class Timer:
    def __init__(self, decimal_places=5):
        self._decimal_places = decimal_places
        self._timer = CoreTimer()
        self._pause = CoreTimer()
        self._pause_length = 0

    def __str__(self):
        state = "Paused" if self._pause else "Running"
        return f"{state} Timer at {self.elapsed_rounded()} seconds"

    @classmethod
    def run(cls, *args, **kwargs):
        self = cls(*args, **kwargs)
        self.start()
        return self

    def start(self):
        if not self._timer:
            self._timer.start()

    def stop(self):
        self._timer.stop()
        self._pause.stop()
        self._pause_length = 0

    def restart(self):
        self.stop()
        self.start()

    def pause(self):
        if not self._pause:
            self._pause.start()

    def resume(self):
        if self._pause:
            self._pause_length += self._pause.elapsed()
            self._pause.stop()

    def elapsed(self):
        if self._pause:
            duration = self._timer.time_taken(self._pause.start_time)
        else:
            duration = self._timer.elapsed()
        return duration - self._pause_length

    def elapsed_rounded(self, decimal_places=None):
        if decimal_places is None:
            decimal_places = self.decimal_places
        return round(self.elapsed(), ndigits=decimal_places)

  • 1

    Спасибо за вашу точку зрения, это действительно полезно (многие улучшения я еще не рассматривал)! Я должен был указать, что таймер не будет использоваться пользователем напрямую, только распечатки get_timer видны пользователю. Поэтому я включил как можно больше удобства для себя. Учитывая это, вы бы по-прежнему не советовали использовать удобные методы для печати и перезапуска? Что-то типа print_elapsed_and_restart?

    — рискованный пингвин


  • 1

    @riskypenguin Под «пользователем» я подразумеваю «любого, кто использует Timer«не« никому, кто пользуется вашей библиотекой ». Я понимаю стремление к удобству. Но часто то, что вы считаете удобным в начале проекта, становится неудобством к концу проекта.

    — Пейлонрайз

  • 1

    Я понимаю вашу точку зрения, еще раз спасибо за исчерпывающий обзор!

    — рискованный пингвин


Что касается вашего вопроса об инициализации атрибута:

Необязательно помещать все атрибуты в __init__. Обычно это рекомендуется, поскольку мы ожидаем найти все атрибуты класса внутри __init__, но я бы сказал, что в этом случае он так же удобочитаем. Фактически, я бы сказал, что инициализация этих значений с помощью None внутри __init__ (просто чтобы подавить предупреждения или следовать рекомендациям) ухудшает читаемость в вашем случае:

def __init__(self, default_round_digits: int = 5):
    self.default_digits = default_round_digits

    self.start_time = None
    self.pause_start_time = None
    self.pause_length = None

    self.start_timer()

Вот еще несколько обсуждений на StackOverflow по этой теме:

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *