python任务调度之schedule_python任务调度之schedule

本文通过开源项目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实现的,有很多很好用的函数.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值