6

I have a function that I want to trigger at every turn of the minute — at 00 seconds. It fires off a packet over the air to a dumb display that will be mounted on the wall.

I know I can brute force it with a while loop but that seems a bit harsh.

I have tried using sched but that ends up adding a second every minute.

What are my options?

tshepang
  • 12,111
  • 21
  • 91
  • 136
user2929831
  • 73
  • 1
  • 1
  • 5
  • 6
    Seems like a good job for a crontab. – Paulo Scardine Oct 28 '13 at 21:51
  • @yuvi a while loop will eat up a processor. – Cody Piersall Oct 28 '13 at 21:51
  • @CodyPiersall depending on the magnitude of the project I'd have to say – yuvi Oct 28 '13 at 21:52
  • 1
    @CodyPiersall While not necessarily the ideal way, a simple `time.sleep(x)` will mitigate any significant overhead of a dumb while-loop. – user2864740 Oct 28 '13 at 21:53
  • +1 @PauloScardine - the Python script doesn't even need a loop then, and cron will keep it synced up without burning CPU. And since it restarts every minute, an unexpected error won't keep it down until you restart manually. Best to keep it simple. – Izkata Oct 28 '13 at 21:56

9 Answers9

7

You might try APScheduler, a cron-style scheduler module for Python.

From their examples:

from apscheduler.scheduler import Scheduler

# Start the scheduler
sched = Scheduler()
sched.start()

def job_function():
    print "Hello World"

sched.add_cron_job(job_function, second=0)

will run job_function every minute.

rolacher
  • 68
  • 1
  • 4
Christian Ternus
  • 8,406
  • 24
  • 39
  • 1
    Great module, the syntax has changed since the answer, see: http://apscheduler.readthedocs.io/en/v3.5.1/modules/schedulers/base.html#apscheduler.schedulers.base.BaseScheduler.add_job and http://apscheduler.readthedocs.io/en/v3.5.1/modules/triggers/cron.html – chjortlund Apr 16 '18 at 13:46
5

What if you measured how long it took your code to execute, and subtracted that from a sleep time of 60?

import time

while True:
    timeBegin = time.time()

    CODE(.....)

    timeEnd = time.time()
    timeElapsed = timeEnd - timeBegin
    time.sleep(60-timeElapsed)
Tizzee
  • 171
  • 1
  • 8
  • 4
    There's a possibility that 60-timeElapsed could be negative, which throws an IOError. time.sleep(max(0, 60-timeElapsed)) can fix that. – Jordan Nov 02 '16 at 14:56
4

The simplest solution would be to register a timeout with the operating system to expire when you want it to.

Now there are quite a few ways to do so with a blocking instruction and the best option depends on your implementation. Simplest way would be to use time.sleep():

import time

current_time = time.time()
time_to_sleep = 60 - (current_time % 60)
time.sleep(time_to_sleep)

This way you take the current time and calculate the amount of time you need to sleep (in seconds). Not millisecond accurate but close enough.

immortal
  • 3,118
  • 20
  • 38
  • Does time.sleep work even if the operating system/computer sleeps? – theonlygusti Dec 26 '16 at 10:18
  • @theonlygusti I doubt that... When the computer sleeps no application can get CPU time as the CPU is also asleep. There are HW interrupts that could wake the computer up, usually a mechanism wired to the Laptop's display and detects the computer was opened, and many network cards can issue such an interrupt by receiving a packet / access through IPMI interface. The time.sleep function, however, requests the operating system to suspend execution for a period of time. It will not issue a wakeup call to the entire machine. – immortal Jan 01 '17 at 08:38
4

APScheduler is the correct approach. The syntax has changed since the original answer, however.

As of APScheduler 3.3.1:

def fn():
    print("Hello, world")

from apscheduler.schedulers.background import BackgroundScheduler

scheduler = BackgroundScheduler()
scheduler.start()
scheduler.add_job(fn, trigger='cron', second=0)
Daniel Jones
  • 615
  • 5
  • 17
3

The syntax has been changed, so in APScheduler of version 3.6.3 (Released: Nov 5, 2019) use the following snippet:

from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger

def fn():
    print('Hello, world!')


sched = BlockingScheduler()

# Execute fn() at the start of each minute.
sched.add_job(fn, trigger=CronTrigger(second=00))
sched.start()
2

You can try Threading.Timer

See this Example

from threading import Timer

def job_function():
    Timer(60, job_function).start ()
    print("Running job_funtion")

It will print "Running job_function" every Minute

Edit: If we are critical about the time at which it should run

from threading import Timer
from time import time


def job_function():
    Timer(int(time()/60)*60+60 - time(), job_function).start ()
    print("Running job_funtion")

It will run exactly at 0th second of every minute.

Anshuman
  • 421
  • 4
  • 11
0

The Python time module is usually what I use for events like this. It has a method called sleep(t), where t equals the time in seconds you want to delay.

Combined with a while loop, you can get what you're looking for:

import time

while condition:
    time.sleep(60)
    f(x)
samrap
  • 5,595
  • 5
  • 31
  • 56
  • 5
    This doesn't account for the time that f(x) takes and so it will have a similar "adding a second every minute" problem the poster wants to avoid. – tdelaney Oct 28 '13 at 22:26
0

May use this example:

   def do():
        print("do do bi do")

   while True:
        alert_minutes= [15,30,45,0]
        now=time.localtime(time.time())
        if now.tm_min in alert_minutes:
        do()
        time.sleep(60)
Baris
  • 3
  • 3
-1

you could use a while loop and sleep to not eat up the processor too much

Kevin
  • 750
  • 2
  • 10
  • 20