Python sched Scheduler — Core Concepts
What sched does
The sched module provides a general-purpose event scheduler. You create a scheduler, add events with times and callbacks, then run the scheduler. It processes events in chronological order, sleeping between them.
import sched
import time
s = sched.scheduler(time.time, time.sleep)
def greet(name):
print(f"{time.strftime('%H:%M:%S')} Hello, {name}!")
s.enter(2, 1, greet, argument=("Alice",))
s.enter(5, 1, greet, argument=("Bob",))
s.run()
enter(delay, priority, action, argument, kwargs) schedules a callback to run after delay seconds. Lower priority numbers run first if two events have the same time.
Relative vs absolute scheduling
| Method | Time reference | Use case |
|---|---|---|
enter(delay, ...) | Relative — seconds from now | ”Do this in 30 seconds” |
enterabs(time, ...) | Absolute — epoch timestamp | ”Do this at 14:30:00” |
import sched, time
s = sched.scheduler(time.time, time.sleep)
# Absolute: run at a specific epoch time
target = time.time() + 10 # 10 seconds from now
s.enterabs(target, 1, print, argument=("Fired!",))
s.run()
enterabs is useful when you’ve calculated exact timestamps, like “next hour on the dot.”
Cancellation
enter and enterabs return an event object you can cancel before it fires:
s = sched.scheduler(time.time, time.sleep)
event = s.enter(60, 1, print, argument=("This won't run",))
s.cancel(event) # removed from the queue
Calling cancel on an already-fired or already-cancelled event raises ValueError.
Priority tie-breaking
When two events are scheduled for the same time, the one with the lower priority number runs first:
s = sched.scheduler(time.time, time.sleep)
s.enter(0, 2, print, argument=("Second",))
s.enter(0, 1, print, argument=("First",))
s.run()
# Output: First, then Second
Inspecting the queue
s = sched.scheduler(time.time, time.sleep)
s.enter(10, 1, print, argument=("A",))
s.enter(5, 1, print, argument=("B",))
print(s.queue) # list of Event namedtuples, sorted by time
print(s.empty()) # False
s.queue returns a sorted list of pending events. Each event is a named tuple with time, priority, sequence, action, argument, kwargs.
Repeating events
sched doesn’t have a built-in repeat mechanism, but you can reschedule inside the callback:
import sched, time
s = sched.scheduler(time.time, time.sleep)
def heartbeat():
print(f"{time.strftime('%H:%M:%S')} ping")
s.enter(5, 1, heartbeat) # reschedule itself
s.enter(0, 1, heartbeat) # start immediately
s.run()
This creates an infinite loop of events firing every 5 seconds. Add a condition to stop when needed.
Custom time functions
The scheduler takes two functions: a “time” function and a “delay” function. You can substitute these for testing or simulation:
fake_time = [0]
def get_time():
return fake_time[0]
def advance(seconds):
fake_time[0] += seconds
s = sched.scheduler(get_time, advance)
s.enter(10, 1, print, argument=("Event at t=10",))
s.run() # runs instantly — fake time jumps forward
This is powerful for testing: you can verify scheduling logic without waiting real seconds.
Common misconception
Many developers think sched runs events in background threads. It doesn’t. s.run() blocks the calling thread. If you need non-blocking scheduling, run the scheduler in a separate thread:
import threading
t = threading.Thread(target=s.run, daemon=True)
t.start()
When sched is enough vs when it’s not
| Scenario | sched? | Better alternative |
|---|---|---|
| Simple script with timed events | ✅ | |
| Testing time-dependent logic | ✅ | |
| Recurring jobs in production | ❌ | Celery Beat, APScheduler, cron |
| Thousands of concurrent timers | ❌ | asyncio event loop |
| Distributed task scheduling | ❌ | Celery, Temporal, Airflow |
| GUI event loops | ❌ | Framework’s built-in scheduler |
sched is a single-process, single-thread tool. It’s not designed for scale, persistence, or fault tolerance. For production workloads, tools like APScheduler or Celery Beat provide persistence, missed-job recovery, and distributed execution.
The one thing to remember: sched is Python’s built-in event scheduler — perfect for simple timed tasks and testing, but reach for APScheduler or Celery when you need persistence, concurrency, or fault tolerance.
See Also
- Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
- Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
- Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
- Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
- Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.