考虑一个问题, 我需要每次在整点/某个精确时间间隔执行一项任务, 要怎么做?
闭门造车一番
造车完成之后, 翻翻大佬们的博客, 比如这个就很惭愧, 还差好多, 合辙失败
简单的内循环方法
理论上, 精确的定时程序中, mainloop是必要的, 如
import time
counter = 0
status = True
while(True):
t = time.time()
if (not status) and t%5<1:
# call sth
print(t, status, counter)
counter+=1
status = False
if (status and t%5>=1):
status = True #reset
if counter>6:
break
但其有如下缺点:
- 如果每段需要定时的程序都加入加入这个maiinloop中太过繁琐
- 在内含代码层次多时会大幅度降低可读性, 提高维护难度
- 不够酷炫
函数化
考虑可以将其作为一个函数, 并参数化
def timer(func, dt=3, max_iter=5):
counter = 0
status = True
while(True):
t = time.time()
if (not status) and t%dt<0.1:
func()
counter+=1
status = True
if (status and t%dt>=0.1):
status = False #reset
if counter>max_iter:
break
def func():
print('xxx')
timer(func)
functool
虽然泛用性有了长进, 但简单函数在处理有参数的函数的时候会陷入困难,如:
import numpy as np
def func1(fixed_data):
print(np.mean(fixed_data) + time.time())
这时可以考虑引入functool来解决这个问题:
from functool import partial
d = np.random.rand(20)
f1 = partial(func1, fixed_data=d)
timer(func1)
decorator
上面functool方法依然只能处理固定数据的简单情况, 即函数的参数是非时变且能预先知道的情况
def timerd(func, dt=3, max_iter=5):
def wrapper(*args, **kwargs):
counter = 0
status = True
while(True):
t = time.time()
if (not status) and t%dt<0.1:
func(*args, **kwargs)
counter+=1
status = True
if (status and t%dt>=0.1):
status = False #reset
if counter>max_iter:
break
return wrapper
@timerd
def fun1(x):
print(x+'x', time.time())
def fun2b(x):
print(x+'y')
fun1('x')
fun2 = timerd(fun2b, dt=2, max_iter=5)
fun2('x')
可以看出, 经过timerd装饰的函数能够在定时执行的基础上接受参数, 可喜可贺可喜可贺.
decorator的局限性
到现在为止虽然已经有了很大进步, 但依然能做到更好
回到最初的问题, 想象在每分钟开始时执行很多任务ABCD, 按照mainloop的方法:
fun_list = [A, B, C, D]
import time
counter = 0
status = True
max_iter=100
dt=60
while(True):
t = time.time()
if (not status) and t%dt<1:
# call sth
resa = A(sensora(),resd)
resb = B(sensorb(), resa)
resc = C(resa, resb)
resd = D(resc)
print(t, status, counter)
counter+=1
status = False
if (status and t%dt>=1):
status = True #reset
if counter>max_iter:
break
对于这种逻辑关联密切的函数, 直接进行抽象显然不可行, 那么将其转化为一个函数如何?
fun_list = [A, B, C, D]
# 别试了,,没有用
# fun_listd = [timerd(x, dt=60, max_iter=100) for x in fun_list]
def ABCD(sensora, sensorb, resd):
#global resd
resa = A(sensora(),resd)
resb = B(sensorb(), resa)
resc = C(resa, resb)
resd = D(resc)
return resa, resb, resc, resd
ABCD1 = timerd(ABCD)
ABCD1(sensora=sensora, sensorb=sensorb, resd=???)
此时ABCD1已经无法正常返回值resd了, 调用也无从谈起, 这是自指系统本身的缺陷
当然, 使用global可以解决该例中的问题, 但我们可以尝试点别的, 比如coroutine, 它将似李重未体验过的船新版本
coroutine
逻辑定义
import time
def sensora():
return round(time.time()%10,2)
def sensorb():
return round((time.time()+2)*3.412%11,2)
def A(x,y):
return x+y
def B(x,y):
return x+2*y
C = lambda x,y:2*x+y
D = lambda x:x*x
组件定义
def producer_time(next_coroutine,max_iter=100, dt=5):
counter = 0
status = True
while(True):
t = time.time()
if (not status) and t%dt<0.1:
next_coroutine.send('start') # 这里可以传进参数, 虽然这里没有必要; 这说明其仍有额外的自由度
counter+=1
status = True
if (status and t%dt>=0.1):
status = False #reset
if counter>max_iter:
break
def processA(A, next_coroutine, sensora, resd):
try:
while True:
s = (yield)
if s=='start':
resa = A(sensora(),resd)
next_coroutine.send(resa)
except GeneratorExit:
next_coroutine.close()
def processB(B, next_coroutine, sensorb):
try:
while True:
resa = (yield)
resb = B(sensorb(),resa)
next_coroutine.send((resa, resb))
except GeneratorExit:
next_coroutine.close()
def processC(C, next_coroutine):
try:
while True:
resa,resb = (yield)
resc = C(resa,resb)
next_coroutine.send(resc)
except GeneratorExit:
next_coroutine.close()
def processD(D, next_coroutine):
global resd
try:
while True:
resc = (yield)
resd = D(resc)
next_coroutine.send(resd)
except GeneratorExit:
next_coroutine.close()
def consumer():
try:
while True:
resd = (yield)
print('d:',resd)
except GeneratorExit:
pass
连接与运行
resd=0
fin = consumer()
fin.__next__()
pD = processD(D,fin)
pC = processC(C,pD)
pB = processB(B,pC,sensorb)
pA = processA(A,pB,sensora,resd)
pD.__next__()
pC.__next__()
pB.__next__()
pA.__next__()
mloop = producer_time(pA, max_iter=10, dt=2)
mloop()
改进
在编码改进的过程中存在的问题:
- process的定义存在大量重复, 但完全消除这些冗余很复杂以至于工作量几乎在成本以上
- processB中这种需要传入多个协程参数的情况应该存在更好的应对方法
- 仍然无法解决resd的问题, 这是函数式编程的通病: 难以传递公共状态
- 需要对基础函数AB进行调整/规范化, 这是不希望看到的
- 为了处理分发问题加入了列表与逻辑判断, 这或许应该通过构造分发器解决?
- 函数参数传入的顺序需要人工确定编排, 效率低易出错
组建的定义与连接调整如下:
import time
def sensora():
return round(time.time()%10,2)
def sensorb():
print('sensorb called')
return round((time.time()+2)*3.412%11,2)
def A(x,y):
return x()+y
def B(x,y):
return x+2*y()
C = lambda x,y:2*x+y
D = lambda x:x*x
#%%
def producer_time(next_coroutine,max_iter=100, dt=5):
counter = 0
status = True
while(True):
t = time.time()
if (not status) and t%dt<0.1:
next_coroutine.send('run') # 这里可以传进参数, 虽然这里没有必要; 这说明其仍有额外的自由度
counter+=1
status = True
if (status and t%dt>=0.1):
status = False #reset
if counter>max_iter:
break
#%%.
#def func2process_single(func, next_couroutine, *args, **kwargs): 名字太长了
def f2p1(func, next_coroutine, *args, **kwargs):
try:
while True:
s = (yield)
if s=='run':
result = func(*args, **kwargs)
else:
result = func(s, *args, **kwargs)
if type(next_coroutine) in [list, tuple]:
for x in next_coroutine:
x.send(result)
else:
next_coroutine.send(result)
except GeneratorExit:
if type(next_coroutine) in [list, tuple]:
for x in next_coroutine:
x.close()
else:
next_coroutine.close()
def f2p2(func, next_coroutine, *args, **kwargs):
try:
while True:
# 可以根据接收yield数目进一步参数化
s1 = (yield)
s2 = (yield)
result = func(s1, s2, *args, **kwargs)
if type(next_coroutine) in [list, tuple]:
for x in next_coroutine:
x.send(result)
else:
next_coroutine.send(result)
except GeneratorExit:
if type(next_coroutine) in [list, tuple]:
for x in next_coroutine:
x.close()
else:
next_coroutine.close()
def consumer():
global resd
try:
while True:
resd = (yield)
print('d:',resd)
except GeneratorExit:
pass
#%%
resd=0
fin = consumer()
fin.__next__()
pD = f2p1(D,fin)
pC = f2p2(C,pD)
pB = f2p1(B,pC,sensorb)
pA = f2p1(A,[pB,pC],sensora,resd)
pD.__next__()
pC.__next__()
pB.__next__()
pA.__next__()
mloop = producer_time(pA, max_iter=10, dt=2)
分发器原型
def dispatcher(next_coroutines):
try:
while True:
s = (yield)
for x in next_coroutines:
x.send(result)
except GeneratorExit:
for x in next_coroutine:
x.close()