Я реализовал удобный класс, чтобы рассчитать время выполнения кода и распечатать его для пользователя. Это часть более крупного проекта, который будет распространяться среди внешних пользователей. Однако таймер вызывается только другими процедурами, а не пользователем напрямую. Мне было интересно реализовать это сам, поэтому я не ищу внешний модуль или аналогичный. Строки документации здесь опущены, функции довольно просты.
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 ответа
Ваш
Timer
класс содержит два таймера.- Таймер всей продолжительности.
- Таймер для отслеживания пауз.
Я бы вынес код основного таймера в отдельный класс. И мы видим, что вы начинаете повторяться.
Инициализация таймера не должна запускать таймер. Должен ли я сразу начать работать, если я открою приложение таймера?
Если я хочу создать несколько таймеров, но не хочу, чтобы таймер запускался, ваш класс не дает мне никаких вариантов. Однако просто не звоню
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()
start_timer
перезапускает таймеры. Вам следует переименовать метод или заставить его работать так, как ожидается.Если вы позвоните
pause_timer
дважды без звонкаresume_timer
ваш код действует так, как будто таймер никогда не ставился на паузу в первый раз.Я действительно не вижу смысла делать
elapsed_seconds
недвижимость.get_timer
делает слишком много и нарушает принцип SRP.- Округляет истекшее время до указанного количества десятичных знаков.
- Печатает истекшее время.
- Перезапускает таймер.
- Возвращает истекшее время.
Вы должны создать функцию для округления прошедшего времени. Затем оставьте печать и перезапуск для пользователя.
Я бы хотел еще пару методов из функциональности, которую я ожидал бы от таймера.
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)
Что касается вашего вопроса об инициализации атрибута:
Необязательно помещать все атрибуты в __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 по этой теме:
Спасибо за вашу точку зрения, это действительно полезно (многие улучшения я еще не рассматривал)! Я должен был указать, что таймер не будет использоваться пользователем напрямую, только распечатки
get_timer
видны пользователю. Поэтому я включил как можно больше удобства для себя. Учитывая это, вы бы по-прежнему не советовали использовать удобные методы для печати и перезапуска? Что-то типаprint_elapsed_and_restart
?— рискованный пингвин
@riskypenguin Под «пользователем» я подразумеваю «любого, кто использует
Timer
«не« никому, кто пользуется вашей библиотекой ». Я понимаю стремление к удобству. Но часто то, что вы считаете удобным в начале проекта, становится неудобством к концу проекта.— Пейлонрайз
Я понимаю вашу точку зрения, еще раз спасибо за исчерпывающий обзор!
— рискованный пингвин