代码行为优化 —— 尝试以下click-like的写法
TIP
这一章节是笔者突然热血来潮写的
在 关于 saya 的介绍 中,我们提到了这样一个现象
@channel.use(ListenerSchema(listening_events=[GroupMessage]))
async def ero(app: Ariadne, group: Group, message: MessageChain):
if message.display == "涩图来":
...
elif message.display == "涩图去":
...
2
3
4
5
6
为了让这样的写法能够降低一点,Ariadne 出现了很多官方/第三方的 dispatcher 来解决这个问题。 很高兴,我们的用户也的的确确用了这些东西。
但是,在后面的,笔者发现,很多人,其实是没有意识到分开写的精髓。 让我们来好好探究这方面吧
TIP
以下案例改编自真实情节[1]
梦的开始 —— 一个本子下载器
你想做一个本子下载器,然后,你开始思考这个本子下载器该怎么触发
bz rank
bz random [-H24|-D7|-D30]
bz search [-forward] {关键词}
bz download [-forward] {本子号}
2
3
4
然后,顺着这个思路,你写出了这样的 dispatcher,这这样一个函数
TIP
可能有同学好奇为什么不用 Commander
而是 Twilight
, 还记得吗,这个例子改编自真实情节。
@channel.use(
ListenerSchema(
listening_events=[GroupMessage],
inline_dispatchers=[
Twilight(
[
FullMatch("bz"),
UnionMatch("download", "search", "random", "rank")
@ "operation",
ArgumentMatch("-forward", action="store_true", optional=True)
@ "forward_type",
UnionMatch("-H24", "-D7", "-D30", optional=True) @ "rank_time",
WildcardMatch() @ "content",
]
)
]
)
)
async def bz(
app: Ariadne,
group: Group,
member: Member,
operation: RegexResult,
forward_type: ArgResult,
rank_time: RegexResult,
content: RegexResult
):
operation_str = str(operation.result)
if operation_str == "search":
...
elif operation_str == "rank":
...
elif operation_str == "search":
...
elif operation_str == "download":
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
咱们先不聊这个 dispather
太长这个问题(毕竟我们的重点不是这个)
虽然说你用了 Twilight
这类消息链匹配器来使得适配字符串方面你可以剩下很多心。 但是很明显,这样的代码并不是我们想要的,if...elif...elif
这个来来回回的, 跟那 if 涩图来... elif 涩图去...
不就没有任何区别了。
试试 click-like
的方法
Click 是一个利用很少的代码以可组合的方式创造优雅命令行工具接口的 Python 库。 它是高度可配置的,但却有合理默认值的“命令行接口创建工具”。
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo('Hello %s!' % name)
if __name__ == '__main__':
hello()
2
3
4
5
6
7
8
9
10
11
12
这是 click 官方的例子,虽然说 Ariadne 现在并不能做到类似的东西,但是, 这种分类的方法值得我们学习下
我们可以将这四个指令分成四个函数(以下方法究极省略)
@channel.use(...FullMatch("bz rank"))
async def bz_rank(...):
...
@channel.use(...FullMatch("bz search"))
async def bz_search(...):
...
@channel.use(...FullMatch("bz random"))
async def bz_random(...):
...
@channel.use(...FullMatch("bz download"))
async def bz_download(...):
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后下一个问题就出现了,可能函数与函数之间,会有很多相同的代码,怎么办?
那你直接将这些实现包装成一个类不就行了?
class Bz:
def __init__():
...
def encrypt():
...
async def request():
...
async def search():
...
async def download():
...
async def rank():
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
某外星来客的写法
仍然以中这个命令为例子
bz rank
bz random [-H24|-D7|-D30]
bz search [-forward] {关键词}
bz download [-forward] {本子号}
2
3
4
如果你阅读了 Alconna —— 外 星 来 客 章节, 你应该知道这个命令怎么去编写:
from arclet.alconna import Alconna, Args, Option
from arclet.alconna.graia import alcommand, assign
bz = Alconna(
"bz",
Option("rank"),
Option("random", Args["mode", ["-H24", "-D7", "-D30"]]),
Option("search", Args["keyword", str]),
Option("download", Args["id", str]),
Option("-forward")
)
@alcommand(bz)
@assign("rank")
async def rank():
...
@alcommand(bz)
@assign("random")
async def random():
...
@alcommand(bz)
@assign("search")
async def search():
...
@alcommand(bz)
@assign("download")
async def download():
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
但同时,Alconna-Graia 提供了更接近 click-like 的写法:
from arclet.alconna import Args
from arclet.alconna.graia import alc
from graia.ariadne.event.message import GroupMessage
from graiax.shortcut.saya import listen
@listen(GroupMessage)
@alc.command("bz")
@alc.option("rank")
@alc.option("random", Args["mode", ["-H24", "-D7", "-D30"]])
@alc.option("search", Args["keyword", str])
@alc.option("download", Args["id", str])
@alc.option("-forward")
async def bz():
...
2
3
4
5
6
7
8
9
10
11
12
13
14