跳到主要内容

指令的生命周期

会话的概念

为了实现高级的指令功能,我们引入了会话的概念

什么叫会话呢?从用户触发指令开始,到指令结束之间的完整过程,就是一个会话了

在指令会话的不同阶段,我们抽象出了一系列的“钩子”,在会话执行到该阶段时,自动触发,称之为“生命周期”

生命周期的意义

如果你用过 Vue,对 PepperBot 的生命周期一定很眼熟,事实上,PepperBot 的生命周期,就是学习自 Vue

比如 Vue 中的created, mounted,在 PepperBot 中对应的就是 initial

destroyed对应 PepperBot 中的 finish, exit

得益于生命周期,我们可以轻松定义出灵活、高可读性的指令

完整的生命周期及参数,[见此]

对比try except else finally

其实,catchfinishcleanup基本上效果和python自带的exceptelsefinally是一样的

之所以名字并未直接命名为exceptelsefinally,是因为exceptelsefinally是python的关键字,如果用这些作为生命周期的名称,会导致语法错误

假设有这样一段异常捕获

try:
...
except Exception as e:
...
else:
...
finally:
...

等价的生命周期写法

class MyCommand:
async def initial(self): # 对应try,任意非catch、finish、cleanup的方法
...

async def catch(self): # 对应except
...

async def finish(self): # 对应else
...

async def cleanup(self): # 对应finally
...

或者更直观一点

try:
...
except CommandTimeout:
try:
await MyCommand.timeout()
except Exception as e:
await MyCommand.catch(e)
except CommandExit:
try:
await MyCommand.exit()
except Exception as e:
await MyCommand.catch(e)
except Exception as e:
await MyCommand.catch(e)
else:
try:
await MyCommand.finish()
except Exception as e:
await MyCommand.catch(e)
finally:
await MyCommand.cleanup()

如果完全按照python的tryexceptelsefinally的语法,else里的finish,是不会捕获的

不过为了方便开发者,对于finish,我们也会捕获异常

总结一下,就是catch,会捕获除了catch生命周期自身 + cleanup生命周期以外的所有异常,包含timeoutexitfinish

入口initial

什么是入口?就是当用户触发指令时,我们最先执行的方法,即为入口

在 PepperBot 中,入口函数的名称,要求是initial

比如我们有一条触发关键词为"今日头条"的指令

那么,当用户发送“今日头条”时,就会自动触发入口initial钩子

class 今日头条:
async def initial(self):
# 当触发指令时,首先执行initial函数
...

指令超时timeout

我们在声明指令as_command装饰器中,可以设置timeout参数,当用户超过指定的时长未回复机器人时,会自动触发该钩子

class MyCommand:
async def timeout(self):
# 当超时时,触发该钩子
...

对比异常捕获的话,可以理解为

try:
...
except CommandTimeout:
await MyCommand.timeout()

用户主动退出exit

我们在声明指令as_command装饰器中,可以设置exit参数,exit为一个列表,其中每一个元素,都应是一个正则表达式,当用户输入的纯文字内容(即chain.pure_text)满足任意一个正则时,触发exit钩子

class MyCommand:
async def exit(self):
# 当用户主动退出时,触发该钩子
...

对比异常捕获的话,可以理解为

try:
...
except CommandExit:
await MyCommand.exit()

异常捕获catch

在执行指令的任意handler(除catch以外的任意生命周期,或者所有用户定义的方法)时,如果触发异常,如果用户定义了catch,那么则会执行该钩子,有点像是 react 的error boundary

在执行catch生命周期后,根据catch函数的返回指向,可以继续执行指令,也可以直接结束指令,具体见声明指令

class MyCommand:
async def catch(self):
# 当其他handler执行出错时,触发该钩子
...

对比异常捕获的话,可以理解为

try:
...
except CommandTimeout:
try:
await MyCommand.timeout()
except Exception as e:
await MyCommand.catch(e)
except CommandExit:
try:
await MyCommand.exit()
except Exception as e:
await MyCommand.catch(e)
except Exception as e:
await MyCommand.catch(e)

指令自然终止finish

如果我们的指令,在执行过程中,用户响应没有超时,用户也没有主动退出,指令执行也没有出现任何异常,那么,就会触发指令的自然终止钩子

class MyCommand:
async def finish(self):
# 当指令自然退出时,触发该钩子
...

对比异常捕获的话,可以理解为

try:
...
else:
await MyCommand.finish()

指令清理cleanup

类似于finally,指令执行完毕后,会自动触发cleanup钩子

class MyCommand:
async def cleanup(self):
# 当指令执行完毕后,触发该钩子
...

对比异常捕获的话,可以理解为

try:
...
finally:
await MyCommand.cleanup()

复用生命周期的定义

PepperBot 的指令,是基于class的,所以,我们可以基于 mixin,实现生命周期的复用

比如,我们定义了比较通用的生命周期mixin

class CommonMixin:
# 用户主动退出
async def exit(self):
...

# 流程正常退出(在中间的流程return None也是正常退出)
async def finish(self):
...

async def timeout(self):
...

现在,当我们定义不同的指令时,就可以复用生命周期了

class 指令1(CommonMixin):
...

class 指令2(CommonMixin):
...

当指令混入 mixin 后,已经定义的方法是可以覆盖的

比如,我们想在指令1中,自定义一下exit钩子,那么,我们只需要

class 指令1(CommonMixin):
async def exit(self):
...

这样,我们就按照我们的需要,重新定义了exit钩子,其它两个未定义的钩子finish, timeout, 还是保留未被修改的状态,也就是说,“合并了”