揭秘策略模式在Python中的应用与优势

发表时间: 2024-06-13 13:55

前言

match 语法是 pyhton 3.10 新加入的 模式匹配 语法,重点是 模式匹配 ,这不是简单的分支匹配。我其实从来都没有用过它,不过之前有小伙伴说起,就去看看。

实战就是学习的捷径,今天我们通过一个文本控制小球移动的小功能,学习 match 语法。

  • 输入 "走 上 下 右 ",回车后,下方小球会按文本指令行动
  • 也可以输入 "走 上下右"
  • 也可以输入 "走 上-10 下-50-red 右-100" ,控制每一次移动的距离和颜色

源码获取回复 "python" 即可


结构匹配

程序很简单,打开 main.py ,我们只需要在开始的 create_action 函数中,返回各种 Action.方法调用结果即可。

  • 行8:界面每次输入框中按回车,就会触发此函数
  • 行8:参数 input 就是界面输入框的内容

比如,直接调用 build_go ,可以控制小球移动:

现在输入任何内容,小球总是往右移动。

这当然不是我们想要的。现在需要加入一些判断,实现 "走 右" 的指令。

最简单就是使用 if 判断:

注意到吗,我们需要先判断文本按空格划分后的列表结构(必须得有2个元素):

这是我认为 python 中使用 match 语法的一个重要场景,结构匹配(序列匹配)。

所以不要拿那种等值匹配的例子与 match 比较,那种场景用字典匹配不香吗

看看 match 模式匹配:

  • 行9: match 关键字,右边是待验证的值,这里直接按空格分隔。也就是待匹配的是一个列表
  • 行10: case 表达一次验证,右边是验证表达式。这里的匹配表达式同时做了前面 if 的结构匹配逻辑。也就是说,只有值只有2个元素的情况,并且第一个元素值是 "走",才会进入这个 case 。同时,进行解包,变量 dir 是第二个元素的值。
  • 行12:case _ ,通常用于表示不管什么情况,都能匹配,因此一般放到最后。

当然,现在不完全等价前面的 if 写法。此时少了两个细节判断,我们一个个来。

怎么判断输入的第一个元素不是 "走" 的情况?既然如果第一个元素是 "走" 的话,自然进入第一个 case,那么只需要下面补充另一个 case 即可捕捉其他情况:

  • 行13-14:注意,仍然需要捕捉2个元素的输入,因此使用 [go,_] 表示两个元素。这里可以使用其他方式表达,比如 (go, _) go, _
  • 我们希望在提示信息中显示当前输入的第一个元素,因此定义变量即可捕捉。
  • 第二个元素我们不关心,所以用 _

但是,怎么判断第二个元素,是 "上" "下" "左" "右" 中的一个?


条件判断

case 第一个表达式之后,可以接一个 if 判断:

很直观,其实,上面的 case 相当于做了几个事情:

  1. 判断列表得有2个元素
  2. 解包各个元素,并判断第一个元素必须是 "走"。第二个元素到 dir 变量
  3. 判断 dir 变量值

但是,现在如果输入 "走 x" ,无法提示第二个方向输入错误。因此,还要补一个 case:

当然你也可以在第一个 case 里面加判断

现在可以看到 match 的特点:

  • 越是具体的匹配(限制条件),越是靠上
  • 避免了嵌套的判断

不过,上面的代码判断次数上,其实是不等价于 文章开篇的 if 写法。也就是说,其实每个 case 都重复做了 解包、判断长度、判断值等一系列操作。

所以说,用 if 也可以写出一样的整体结构,只不过每个判断结构中的语法没有 match 简洁。

继续完成后续的功能,看看 match 还提供了什么特殊能力。


不定个数匹配

现在只能支持一个方向,太麻烦了,希望 "走 上 下 左 右" ,小球可以按顺序执行行动。那就是说,不仅仅是2个元素,而是:

  • 第一个元素必须是 "走"
  • 后面可以无数个元素。但有限制 "必须合法的方向"

  • 行10: case 模式中,第二个元素 *dirs ,就与 python 的解包规则一样,从第二个元素开始,后续所有元素都被收集成一个列表,放到 dirs 变量中
  • 行11:返回多个指令

现在效果是可以了,但每次输入方向时,每个方向之间需要空格分开,太麻烦了,如果可以直接输入 "走 上下左右" 就好了。

你可能会想,python 中其实字符串就是单个字符的集合,比如下面的代码是成立的:

此时应该总是会走第一个 case 吧。实际上 match 不会做这种隐式转换。如果输入 "走 上下左右" ,会进入第二个 case:


or 模式

case 模式匹配中,可以指定多个匹配模式:

  • 行10:使用 | 可以同时定义多个匹配规则,只要其中一个规则匹配,则会走右边的 if 。注意,多个规则中使用的变量必须相同。

也就是,此时变量 dirs 可能是 str 或 str 列表。从智能提示就可以看出来:

现在 "走" 开始的指令已经可以了。但总是需要先输入 "走" ,太麻烦了,如果直接输入 "上下左右" ,就应该识别出来是 "走" 指令啊。

简单:

目前为止,我们一直在匹配列表(输入内容按空格分隔)。在 match 语法的使用场景中,对字典的匹配,也是我认为的一个重要场景。

上面的例子代码,在 main.py 文件中。字典匹配的实现在 main_by_dict_match.py


字典匹配

我们把解析流程修改一下:

  • 专门有函数负责第一层解析,返回一个字典。
  • 字典总有一个 action 键值对,表示具体指令。比如 "走"、"还原" 等
  • 针对不同的命令,字典有不同的键值对

解析成字典的函数,问人工智能"好朋友"就可以了:

  • 用到的都是前面的知识

单独实现函数的好处是,很容易测试和定位问题:

有了命令信息字典,现在看看怎么使用 match 对字典匹配:

  • 行10:match 右边填入字典
  • 行11:字典的匹配模式很简单,只要字典中包含列出的键值对,就通过(还需要通过右边的 if 判断)。这里,只要字典有 action 和 cmds 的 key,并且 action 值是 "走" ,即可匹配。后面的 if 判断进一步验证里面的 cmds

场景

每个新语法,都是为了解决某种场景而设计。从这个案例中可以看到,我们不必把所有处理逻辑集中到一个 match 中。而是适当把逻辑拆分一下。

对于非结构匹配需求,我认为没太多必要使用 match 。而是可以考虑使用字典匹配。

match 最大的限制在于, case 表达式是固定的,无法在程序运行的时候动态改变。因此它适合用在固定模式的匹配上。

不要忘记一键三连。你的点赞、收藏、关注,是我创作的动力。