问题: 希望编写回调函数可以携带额外的状态以便在回调函数内部使用
首先定义一个回调函数:
def apply_async(func, args, *, callback):
result = func(*args)
callback(result)
使用示例:
def print_reqult(result):
""" 仅接受一个单独的函数 """
print('Got:', result)
def add(x, y):
return x + y
apply_async(add, (1, 2), callback=print_reqult)
Got: 3
有些时候我们希望回调函数可以同其他变量或者部分环境进行交互。
解决方案:一种在回调函数中携带额外信息的方法是使用绑定方法而不是普通的函数
方案1:比如,下面这个类保存了一个内部序列号码,每当接收到一个结果时就递增这个号码
class ResultHanlder:
def __init__(self):
self.sequence = 0
def handler(self, result):
self.sequence +=1
print('[{}] Got: {}'.format(self.sequence, result))
r = ResultHanlder()
apply_async(add, (1, 2), callback=r.handler)
[1] Got: 3
方案2:作为类的替代方案,也可以使用闭包来捕获状态
def make_result():
sequence = 0
def hanlder(result):
nonlocal sequence
sequence += 1
print('[{}] Got: {}'.format(sequence, result))
return hanlder
hanlder = make_result()
apply_async(add, (1, 2), callback=hanlder)
[1] Got: 3
方案3:有时候可以利用协程(coroutine)来完成同样的任务
def make_result():
sequence = 0
while True:
result = yield
sequence += 1
print('[{}] Got: {}'.format(sequence, result))
# 对于协程来说,可以使用它的send()方法来作为回调函数
hanlder = make_result()
next(hanlder) # 在使用协程前需要对其调用一次next()
apply_async(add, (1, 2), callback=hanlder.send)
[1] Got: 3
方案4:也可以通过额外的参数在回调函数中携带状态,然后使用partial()来处理参数个数的问题
class SequenceNo:
def __init__(self):
self.sequence = 0
def hanlder(result, seq):
seq.sequence += 1
print('[{}] Got: {}'.format(seq.sequence, result))
seq = SequenceNo()
from functools import partial
apply_async(add, (1, 2), callback=partial(hanlder, seq=seq))
[1] Got: 3
方案5:使用lambda表达式来实现上述相同的功能
apply_async(add, (1, 2), callback=lambda r: hanlder(r, seq))
[2] Got: 3
总结:
1、主要有两种方法可用于捕获和携带状态:
- 在类实例上携带状态(将状态附加到绑定方法上);
- 在闭包中携带状态;
2、如果使用闭包,那么需要对可变变量多加留意。nonlocal声明用来表示变量sequence是在回调函数中修改的;
3、使用协程的好处:
- 更加清晰;
- 变量可以自由的修改,不必担心nonlocal声明;
- 在使用协程前需要先对其调用一次next()。