python的函数抽象复用--以定时器为例


考虑一个问题, 我需要每次在整点/某个精确时间间隔执行一项任务, 要怎么做?
闭门造车一番

造车完成之后, 翻翻大佬们的博客, 比如这个就很惭愧, 还差好多, 合辙失败

简单的内循环方法

理论上, 精确的定时程序中, 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

但其有如下缺点:

  1. 如果每段需要定时的程序都加入加入这个maiinloop中太过繁琐
  2. 在内含代码层次多时会大幅度降低可读性, 提高维护难度
  3. 不够酷炫

函数化

考虑可以将其作为一个函数, 并参数化

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()

改进

在编码改进的过程中存在的问题:

  1. process的定义存在大量重复, 但完全消除这些冗余很复杂以至于工作量几乎在成本以上
  2. processB中这种需要传入多个协程参数的情况应该存在更好的应对方法
  3. 仍然无法解决resd的问题, 这是函数式编程的通病: 难以传递公共状态
  4. 需要对基础函数AB进行调整/规范化, 这是不希望看到的
  5. 为了处理分发问题加入了列表与逻辑判断, 这或许应该通过构造分发器解决?
  6. 函数参数传入的顺序需要人工确定编排, 效率低易出错
    组建的定义与连接调整如下:
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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值