Не стесняйтесь заглядывать в исходники. В модуле threading, например, есть отдельный класс (наследник от Thread) для отложенного запуска функций. Вот его код полностью:
class Timer(Thread):
"""Call a function after a specified number of seconds:
t = Timer(30.0, f, args=None, kwargs=None)
t.start()
t.cancel() # stop the timer's action if it's still waiting
"""
def __init__(self, interval, function, args=None, kwargs=None):
Thread.__init__(self)
self.interval = interval
self.function = function
self.args = args if args is not None else []
self.kwargs = kwargs if kwargs is not None else {}
self.finished = Event()
def cancel(self):
"""Stop the timer if it hasn't finished yet."""
self.finished.set()
def run(self):
self.finished.wait(self.interval)
if not self.finished.is_set():
self.function(*self.args, **self.kwargs)
self.finished.set()
Собственно, это идеальное решение на чистых тредах.
Если бы вы использовали, скажем, торнадо или aiohttp в качестве асинхронного фреймворка, то там уже есть готовый EventLoop и специальные методы для помещения в очередь задач, запланированных на запуск в конкретное время или через заданный интервал времени однократно или периодически.
Для простых целей, конечно, правильнее просто решить вопрос стандартной библиотекой. Есл у вас более сложная комплексная задача, то, возможно, стоит задуматься об асинхронном фреймворке, который избавит вас от написания, отладки и поддержки довольно стандартного кода.