本文通过开源项目schedule来学习定时任务如何工作

schedule简介

先来看下做做提供的一个例子

import schedule
import time def job():
print("I'm working...") schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)
schedule.every().minute.at(":17").do(job) while True:
schedule.run_pending()
time.sleep(1)

注解

  • 每隔10分钟执行一次任务
  • 每隔一个小时执行一次任务
  • 每天10:30执行一次任务
  • 每周一执行一次任务
  • 每周三13:15执行一次任务
  • 每小时的第17分钟时执行一次任务

schedule源码学习

可看到有三个类CancelJob、Scheduler、Job,对源码的分析也将围绕这三个类展开

CancelJob

class CancelJob(object):
pass

可以看到这是一个空类,这个类的作用是当job执行函数返回一个CancelJob类型的对象时,执行完之后就会被Schedule移除,简单说就是只会执行一次

Scheduler

Scheduler类源码

这里为使代码精简、紧凑,删除了注释

class Scheduler(object):
def __init__(self):
self.jobs = []
def run_pending(self):
runnable_jobs = (job for job in self.jobs if job.should_run)
for job in sorted(runnable_jobs):
self._run_job(job)
def run_all(self, delay_seconds=0):
for job in self.jobs:
self._run_job(job)
time.sleep(delay_seconds)
def clear(self):
del self.jobs[:]
def cancel_job(self, job):
try:
self.jobs.remove(job)
except ValueError:
pass
def every(self, interval=1):
job = Job(interval)
self.jobs.append(job)
return job
def _run_job(self, job):
ret = job.run()
if isinstance(ret, CancelJob) or ret is CancelJob:
self.cancel_job(job)
@property
def next_run(self):
if not self.jobs:
return None
return min(self.jobs).next_run
@property
def idle_seconds(self):
return (self.next_run - datetime.datetime.now()).total_seconds()

Scheduler作用

就是在job执行的时候执行它

函数解释

  • run_pending:运行所有可以运行的任务
  • run_all:运行所有任务,不管是否应该运行
  • clear:删除所有调度的任务
  • cancle_job:删除一个任务
  • every:创建一个调度任务,返回一个job
  • _run_joib:运行一个job
  • next_run:获取下一个要运行任务的时间,这里使用的min去得到最近将执行的job,之所以这样使用,是Job重载了__lt__方法,这样写起来很简洁
  • idle_second:还有多少秒即将开始运行任务

Job

Job是整个定时任务的核心. 主要功能就是根据创建Job时的参数,得到下一次运行的时间. 代码如下,稍微有点长(会省略部分代码,可以看源码):

class Job(object):
def __init__(self, interval):
self.interval = interval # pause interval * unit between runs
self.job_func = None # the job job_func to run
self.unit = None # time units, e.g. 'minutes', 'hours', ...
self.at_time = None # optional time at which this job runs
self.last_run = None # datetime of the last run
self.next_run = None # datetime of the next run
self.period = None # timedelta between runs, only valid for
self.start_day = None # Specific day of the week to start on
def __lt__(self, other):
return self.next_run < other.next_run
def minute(self):
assert self.interval == 1, 'Use minutes instead of minute'
return self.minutes
@property
def minutes(self):
self.unit = 'minutes'
return self
@property
def hour(self):
assert self.interval == 1, 'Use hours instead of hour'
return self.hours
@property
def hours(self):
self.unit = 'hours'
return self
@property
def day(self):
assert self.interval == 1, 'Use days instead of day'
return self.days
@property
def days(self):
self.unit = 'days'
return self
@property
def week(self):
assert self.interval == 1, 'Use weeks instead of week'
return self.weeks
@property
def weeks(self):
self.unit = 'weeks'
return self
@property
def monday(self):
assert self.interval == 1, 'Use mondays instead of monday'
self.start_day = 'monday'
return self.weeks
def at(self, time_str):
assert self.unit in ('days', 'hours') or self.start_day
hour, minute = time_str.split(':')
minute = int(minute)
if self.unit == 'days' or self.start_day:
hour = int(hour)
assert 0 <= hour <= 23
elif self.unit == 'hours':
hour = 0
assert 0 <= minute <= 59
self.at_time = datetime.time(hour, minute)
return self
def do(self, job_func, *args, **kwargs):
self.job_func = functools.partial(job_func, *args, **kwargs)
try:
functools.update_wrapper(self.job_func, job_func)
except AttributeError:
# job_funcs already wrapped by functools.partial won't have
# __name__, __module__ or __doc__ and the update_wrapper()
# call will fail.
pass
self._schedule_next_run()
return self
@property
def should_run(self):
return datetime.datetime.now() >= self.next_run
def run(self):
logger.info('Running job %s', self)
ret = self.job_func()
self.last_run = datetime.datetime.now()
self._schedule_next_run()
return ret
def _schedule_next_run(self):
assert self.unit in ('seconds', 'minutes', 'hours', 'days', 'weeks')
self.period = datetime.timedelta(**{self.unit: self.interval})
self.next_run = datetime.datetime.now() + self.period
#还有很多....

参数的含义:

  • interval:间隔多久,每interval秒或分等
  • job_func:job执行函数
  • unit:间隔单元,比如minutes,hours
  • at_time:job具体执行时间点,比如10:30等
  • last_run:job上次执行时间
  • next_run:job下一次即将执行时间
  • period:距离下次运行间隔时间
  • start_day:周的特殊天,也就是monday等的含义

    各种方法
  • __lt__:比较哪个job最先即将执行, Scheduler中next_run方法里使用min会用到, 有时合适的使用python这些特殊方法可以简化代码,看起来更pythonic.
  • second、seconds的区别就是second时默认interval ==1,即schedule.every().second和schedule.every(1).seconds是等价的,作用就是设置unit为seconds. minute和minutes、hour和hours、day和days、week和weeks也类似.
  • monday: 设置start_day 为monday, unit 为weeks,interval为1. 含义就是每周一执行job. 类似 tuesday、wednesday、thursday、friday、saturday、sunday一样.
  • at: 表示某天的某个时间点,所以不适合minutes、weeks且start_day 为空(即单纯的周)这些unit. 对于unit为hours时,time_str中小时部分为0.
  • do: 设置job对应的函数以及参数, 这里使用functools.update_wrapper去更新函数名等信息.主要是functools.partial返回的函数和原函数名称不一样.具体可以看看官网文档. 然后调用_schedule_next_run去计算job下一次执行时间.
  • should_run: 判断job是否可以运行了.依据是当前时间点大于等于job的next_run
  • _schedule_next_run: 这是整个job的定时的逻辑部分是计算job下次运行的时间点的.描述一下流程:
  • 计算下一次执行时间:

这里根据unit和interval计算出下一次运行时间. 举个例子,比如schedule.every().hour.do(job, message='things')下一次运行时间就是当前时间加上一小时的间隔.

  • 但是当start_day不为空时,即表示某个星期. 这时period就不能直接加在当前时间了. 看代码:

其中days_ahead表示job表示的星期几与当表示的星期几差几天. 比如今天是星期三,job表示的是星期五,那么days_ahead就为2,最终self.next_run效果就是在now基础上加了2天.

  • 当at_time不为空时, 需要更新执行的时间点,具体就是计算时、分、秒然后调用replace进行更新. 这里对unit为days或hours进行特殊处理:

当已经过了执行时间的话的话,unit为days的话减去一天, unit为hours的话减去一小时. 这样可以保证任务今天运行.

  • 后面还有一句代码:

这句的含义时对于像monday这些定时任务特殊情况的处理. 举个例子, 今天是星期四12:00,创建的job是星期四13:00, days_ahead <=7 这个条件满足,最终next_run实际加了7,这样的话这个任务就不会运行了. 所以这一步实际就是把7减掉. 看上去有点绕, 实际只要把days_ahead <= 0改为days_ahead < 0这句代码就不用了.

学习总结

通过学习schedule,可以看到实现一个基础的任务定时调度就是根据job的配置计算执行时间和执行job. 代码里我认为比较好的地方有:

  • __lt__的使用,这样min函数直接应用在job上.
  • @property是代码更简洁
  • 返回self支持连缀操作,像schedule.every(10).minutes.do(job)看起来很直接.
  • 时间部分完全是根据datetime实现的,有很多很好用的函数.

的确需要好好学习下

转载:https://zhuanlan.zhihu.com/p/23086148

python任务调度之schedule的更多相关文章

  1. python中使用schedule模块定时执行任务

    python中有一个轻量级的定时任务调度的库:schedule.他可以完成每分钟,每小时,每天,周几,特定日期的定时任务.因此十分方便我们执行一些轻量级的定时任务. 代码如下: import sche ...

  2. python:定时任务模块schedule

    1.安装 pip install schedule 2.文档 https://schedule.readthedocs.io/en/stable/faq.html#how-to-execute-job ...

  3. 一个简单的Python调度器Schedule

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  4. 简单友好的 Python 任务调度库

    schedule :https://github.com/dbader/schedule 该项目人性化的 API 设计,让开发者仅用几行代码就能轻松实现定时任务.它不依赖任何第三方库,全部代码也就一个 ...

  5. Python 资源大全中文版

    Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesome-python 是 vinta 发起维护的 Python 资源列 ...

  6. [转载]Python 资源大全

    原文链接:Python 资源大全 环境管理 管理 Python 版本和环境的工具 p – 非常简单的交互式 python 版本管理工具. pyenv – 简单的 Python 版本管理工具. Vex  ...

  7. python常用库

    本文由 伯乐在线 - 艾凌风 翻译,Namco 校稿.未经许可,禁止转载!英文出处:vinta.欢迎加入翻译组. Awesome Python ,这又是一个 Awesome XXX 系列的资源整理,由 ...

  8. Python 库大全

    作者:Lingfeng Ai链接:http://www.zhihu.com/question/24590883/answer/92420471来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非 ...

  9. Python常用库大全

    环境管理 管理 Python 版本和环境的工具 p – 非常简单的交互式 python 版本管理工具. pyenv – 简单的 Python 版本管理工具. Vex – 可以在虚拟环境中执行命令. v ...

  10. [转]Python 资源大全中文版

    摘自:https://github.com/jobbole/awesome-python-cn 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesom ...

随机推荐

  1. 奥特曼autMan对接BBK-JD扫码登录+微信扫码登录

    1.BBK京东扫码容器配置 首先进入root目录 cd /root 拉取镜像创建容器 docker run -dit \ -v $PWD/conf:/data/conf \ -v $PWD/logs: ...

  2. 无界 | Bncr | Boundless Nodejs Chat Robot 机器人框架安装使用教程

    背景:Bncr 是一个开箱即用的Nodejs Chat RoBot(会话式机器人)框架.它基于OOP函数响应式编程,具有占用小.响应快.开发易等特点,允许开发者创建高度可测试.可扩展.松散耦合且易于维 ...

  3. MySQL - [18] mysql中关于cascade的用法

    drop database语句用于删除数据库.但如果想要删除一个数据库并且还要删除所有依赖于该数据库的存储过程.函数等,可以使用cascade关键字.drop database test cascad ...

  4. 基于 Flink+Iceberg 构建企业级实时数据湖

    Apache Flink 是大数据领域非常流行的流批统一的计算引擎,数据湖是顺应云时代发展潮流的新型技术架构.那么当 Apache Flink 遇见数据湖时,会碰撞出什么样的火花呢?本次分享主要包括以 ...

  5. 【论文随笔】多行为序列Transformer推荐(Multi-Behavior Sequential Transformer Recommender)

    前言 今天读的论文为一篇于2022年7月发表在第45届国际计算机学会信息检索会议(SIGIR '22)的论文,文章主要为推荐系统领域提供了一个新的视角,特别是在处理用户多行为序列数据方面,提出了一种有 ...

  6. wangeditor编辑器

    官网  https://www.wangeditor.com/ 在线体验DEMO   https://codepen.io/xiaokyo-the-bold/pen/ZEpWByR

  7. js回忆录(1) -- 变量,null 和 undefined

    变量:这个东西不同的高度的人看法不一样,甚至不同领域的人的看法也不一样,当初上机组的时候依稀记得老师说这个寄存器那个锁存器什么的,然后根据高低电位就变成了二进制认识的0和1了,当然了具体细节本博主大人 ...

  8. Wyn商业智能V8.0 Update1版本发布

    智启未来,Wyn商业智能V8.0 Update1版本深度融合AI技术.工业物联网与数据工程能力,带来三大核心亮点及100+新特性,致力于为企业打造全场景智能分析平台,助力构建从数据加工到智能决策的全链 ...

  9. Markdown 中如何添加图片

    语法 ![alt text](uri "optional title") alt text:可选,图片标签,用来描述的关键词,图片加载错误时候的替代文本,也可用于SEO. uri: ...

  10. SQL SERVER日常运维巡检系列之-实例级参数

    前言 做好日常巡检是数据库管理和维护的重要步骤,而且需要对每次巡检日期.结果进行登记,同时可能需要出一份巡检报告. 本系列旨在解决一些常见的困扰: 不知道巡检哪些东西不知道怎么样便捷体检机器太多体检麻 ...