Страницы

Поиск по вопросам

воскресенье, 8 декабря 2019 г.

Как правильно сделать временный цикл?

#python #python_3x #циклы


Нужно сделать цикл, который будет минуту выполнять функцию с промежутком в 1 сек.

Какой способ самый эффективный? Не смог придумать ничего лучше этого:

 from time import sleep

 seconds_left = 60
 while seconds_left > 0:
      do_something()
      seconds_left -= 1
      sleep(1)

    


Ответы

Ответ 1



Можно range(n) использовать, чтобы повторить что-то n раз в Питоне: for _ in range(60): do_something() time.sleep(1) "Правильный" и "эффективный" ли это способ? Это зависит от конкретной задачи. Код прост и понятен: вызвать do_something() функцию 60 раз, каждый следующий вызов происходит не ранее чем через секунду после завершения предыдущего вызова. В конце ждём секунду прежде чем завершить цикл. Представьте do_something() требует полсекунды в среднем, тогда цикл займёт полторы минуты или больше. Хорошо ли это—зависит от задачи. Если вы хотите, чтобы вызовы происходили бы на границе каждой секунды: for _ in range(60): do_something() time.sleep(1 - time.time() % 1) C точностью до активности других процессов, потоков (особенностей планировщиков процессов/потоков в операционной системе, особенностей GIL реализации выбранной версии Питон-интерпретатора), каждый вызов do_something() кроме первого происходит близко ко времени, когда time.time() целые значения возвращает. К примеру, если time.time() вернёт X.3 секунд, то time.sleep() будет спать не менее 0.7 секунд (если обработчик сигналов исключение не выбросит) и значит следующий вызов do_something() произойдёт в X + 1 секунд по системному времени (если оно сильно не прыгнуло пока мы спали). В этом случае вызовы do_something() могут быть более равномерно распределены и цикл завершится почти ровно через минуту согласно time.time() (если время выполнения do_something() менее секунды). Хорошо ли это—зависит от задачи. Если вы не хотите запускать do_something(), если больше минуты прошло согласно выбранному таймеру, можно явное условие использовать: deadline = time.monotonic() + 60 while time.monotonic() < deadline: do_something() time.sleep(1) Вещи, которые необходимо принять во внимание в зависимости от задачи: Какое желаемое поведение, если do_something() может выполняться больше секунды: пропустить итерацию, запустить в отдельном потоке, пуле потоков/процессов? Какое желаемое поведение, если системное время прыгнет (потому что кто-то закрыл крышку ноутбука во время цикла или hibernation системы по любой другой причине произошло или мобильная OS отгрузила ваш процесс, чтобы энергию сэкономить через какое-то время, или код внутри VM—большие прыжки возможны). Хотите ли вы продолжить выполнение цикла как будто ничего не произошло, когда система проснётся, или отменить последующие вызовы (устарели), или выполнить оставшиеся вызовы как можно быстрее, без паузы или вообще все сразу (deadline прошёл)? К примеру, если время изменится во время выполнения print_some_times() из @ReinRaus ответа, то что по вашему произойдёт? Гарантированно ли это поведение документацией sched модуля (не изменится ли оно между разными реализациями/версиями Питона)? Важно ли это в вашем конкретном случае? (думаю, ясность простого цикла со sleep() может стать особенно привлекательной в этот момент). В качестве вариации на тему, можно периодически вызывать функцию в фоновом потоке или использовать возможности различных циклов событий. В более сложных случаях, если у вас нет особых предпочтений, можно использовать apscheduler модуль, чтобы встроить планировщик в ваше приложение. В каких-то случаях имеет смысл воспользоваться планировщиком системы (для больших интервалов) таких как cron и Windows Task Scheduler. Полезно знать причины, по которым call_repeatedly() функцию убрали из PEP 3156 -- Asynchronous IO Support Rebooted: the "asyncio" Module: Note: A previous version of this PEP defined a method named call_repeatedly() , which promised to call a callback at regular intervals. This has been withdrawn because the design of such a function is overspecified. On the one hand, a simple timer loop can easily be emulated using a callback that reschedules itself using call_later() ; it is also easy to write coroutine containing a loop and a sleep() call (a toplevel function in the module, see below). On the other hand, due to the complexities of accurate timekeeping there are many traps and pitfalls here for the unaware (see PEP 418), and different use cases require different behavior in edge cases. It is impossible to offer an API for this purpose that is bullet-proof in all cases, so it is deemed better to let application designers decide for themselves what kind of timer loop to implement.

Ответ 2



import sched, time s = sched.scheduler(time.time, time.sleep) def print_time(): print ("From print_time", time.time()) time.sleep(.2) def print_some_times(): print ( time.time() ) for i in range( 60 ): s.enter(i, 1, print_time, () ) s.run() print ( time.time() ) print_some_times() Достоинством данного подхода является то, что несмотря на длительное выполнение функции print_time (0.2 секунды) запуски функции происходят с очень высокой точностью (у меня с точностью до миллисекунд): 1476319996.9365706 From print_time 1476319996.9434264 From print_time 1476319997.9441211 ... From print_time 1476320014.944284 From print_time 1476320015.9442484 1476320016.1564507

Ответ 3



Пишем декоратор, чтобы делать функции с «лагом» def make_delayed(delay_in_seconds): """ Decorator with parameter for making functions launched with minimal delay between calls """ if delay_in_seconds <= 0.: raise ValueError("Non-positive delay: {}".format(delay_in_seconds)) class CallDelayer: def __init__(self): self.call_event = threading.Event() self.last_call_time = time.time() def launch_with_delay(self, function): def launched_with_delay(*args, **kwargs): self.last_call_time += delay_in_seconds function(*args, **kwargs) wait_sec = self.last_call_time - time.time() self.call_event.wait(wait_sec) print(wait_sec) return launched_with_delay call_delayer = CallDelayer() return call_delayer.launch_with_delay затем декорируем наш do_something и просто вызываем необходимое количество раз do_something_delayed = make_delayed(1.)(do_something) for i in range(60): do_something_delayed() преимущество данного подхода заключается в высокой точности, использовании средств стандартных библиотек, универсальности, минусом может быть то, что декораторы не всем по душе

Ответ 4



Если нужно выполнять с интервалом (между окончанием одного вызова и началом другого) в течение 1 минуты, то тело цикла верно, а вот условие не очень: import time TIME_TO_LOOP = 60 start = time.time() while time.time() < start + TIME_TO_LOOP: do_something() time.sleep(1)

Комментариев нет:

Отправить комментарий