python设计模式之解释器模式
python设计模式之解释器模式
对每个应用来说,至少有以下两种不同的用户分类。
- [ ] 基本用户:这类用户只希望能够凭直觉使用应用。他们不喜欢花太多时间配置或学习应用的内部。对他们来说,基本的用法就足够了。
- [ ] 高级用户:这些用户,实际上通常是少数,不介意花费额外的时间学习如何使用应用的高级特性。如果知道学会之后能得到以下好处,他们甚至会去学习一种配置(或脚本)语言。
- [ ] 能够更好地控制一个应用
- [ ] 以更好的方式表达想法
- [ ] 提高生产力
解释器( Interpreter)模式仅能引起应用的高级用户的兴趣。这是因为解释器模式背后的主要思想是让非初级用户和领域专家使用一门简单的语言来表达想法。然而,什么是一种简单的语言?对于我们的需求来说,一种简单的语言就是没编程语言那么复杂的语言。
一般而言,我们想要创建的是一种领域特定语言( Domain Specific Language, DSL)。 DSL是一种针对一个特定领域的有限表达能力的计算机语言。很多不同的事情都使用DSL,比如,战斗模拟、记账、可视化、配置、通信协议等。
内部DSL构建在一种宿主编程语言之上。内部DSL的一个例子是,使用Python解决线性方程组的一种语言。使用内部DSL的优势是我们不必担心创建、编译及解析语法,因为这些已经被宿主语言解决掉了。劣势是会受限于宿主语言的特性。如果宿主语言不具备这些特性,构建一种表达能力强、简洁而且优美的内部DSL是富有挑战性的。
外部DSL不依赖某种宿主语言。 DSL的创建者可以决定语言的方方面面(语法、句法等),但也要负责为其创建一个解析器和编译器。为一种新语言创建解析器和编译器是一个非常复杂、长期而又痛苦的过程。
解释器模式仅与内部DSL相关。因此,我们的目标是使用宿主语言提供的特性构建一种简单但有用的语言,在这里,宿主语言是Python。注意,解释器根本不处理语言解析,它假设我们已经有某种便利形式的解析好的数据,可以是抽象语法树( abstract syntax tree, AST)或任何其他好用的数据结构。
1. 现实生活的例子
音乐演奏者是现实中解释器模式的一个例子。五线谱图形化地表现了声音的音调和持续时间。音乐演奏者能根据五线谱的符号精确地重现声音。在某种意义上,五线谱是音乐的语言,音乐演奏者是这种语言的解释器。
2. 软件的例子
内部DSL在软件方面的例子有很多。 PyT是一个用于生成(X)HTML的Python DSL。 PyT关注性能,并声称能与Jinja2的速度相媲美。当然,我们不能假定在PyT中必须使用解释器模式。然而, PyT是一种内部DSL,非常适合使用解释器模式。
3. 应用案例
在我们希望为领域专家和高级用户提供一种简单语言来解决他们的问题时,可以使用解释器模式。不过要强调的第一件事情是,解释器模式应仅用于实现简单的语言。如果语言具有外部DSL那样的要求,有更好的工具( yacc和lex、 Bison、 ANTLR等)来从头创建一种语言。
我们的目标是为专家提供恰当的编程抽象,使其生产力更高;这些专家通常不是程序员。理想情况下,他们使用我们的DSL并不需要了解高级Python知识,当然了解一点Python基础知识会更好,因为我们最终生成的是Python代码,但不应该要求了解Python高级概念。此外, DSL的性能通常不是一个重要的关注点。重点是提供一种语言,隐藏宿主语言的独特性,并提供人类更易读的语法。诚然, Python已经是一门可读性非常高的语言,与其他编程语言相比,其古怪的语法更少。
4. 实现
我们来创建一种内部DSL控制一个智能屋。这个例子非常契合如今越来越受关注的物联网时代。用户能够使用一种非常简单的事件标记来控制他们的房子。一个事件的形式为command ->receiver -> arguments。参数部分是可选的。并不是所有事件都要求参数。不要求任何参数的事件例子如下所示。
open -> gate
要求参数的事件例子如下所示:
increase -> boiler temperature -> 3 degrees
->符号用于标记事件一个部分的结束,并声明下一个部分的开始。实现一种内部DSL有多种方式。我们可以使用普通的正则表达式、字符串处理、操作符重载的组合以及元编程,或者一个能帮我们完成困难工作的库/工具。虽然在正规情况下,解释器不处理解析,但我觉得一个实战的例子也需要覆盖解析工作。因此,我决定使用一个工具来完成解析工作。 该工具名为Pyparsing,是标准Python3发行版的一部分。 如果你的系统上还没安装Pyparsing,可以使用下面的命令来
安装。
pip3 install pyparsing
在编写代码之前,为我们的语言定义一种简单语法是一个好做法。我们可以使用巴科斯-诺
尔形式( Backus-Naur Form, BNF)表示法来定义语法。
event ::= command token receiver token arguments
command ::= word+
word ::= a collection of one or more alphanumeric characters
token ::= ->
receiver ::= word+
arguments ::= word+
简单来说,这个语法告诉我们的是一个事件具有command -> receiver -> arguments的形式,并且命令、接收者及参数也具有相同的形式,即一个或多个字母数字字符的组合。包含数字部分是为了让我们能够在命令increase -> boiler temperature -> 3 degrees中传递3 degrees这样的参数,所以不必怀疑数字部分的必要性。
既然定义了语法,那么接着将其转变成实际的代码。以下是代码的样子。
word = Word(alphanums)
command = Group(OneOrMore(word))
token = Suppress("->")
device = Group(OneOrMore(word))
argument = Group(OneOrMore(word))
event = command + token + device + Optional(token + argument)
代码和语法定义基本的不同点是,代码需要以自底向上的方式编写。例如,如果不先为word赋一个值,那就不能使用它。 Suppress用于声明我们希望解析结果中省略->符号。
这个例子的完整代码(文件interpreter.py)使用了很多占位类,但为了让你精力集中一点,我会先只展示一个类。书中也包含完整的代码列表,在仔细解说完这个类之后会展示。我们来看一下Boiler类。一个锅炉的默认温度为83摄氏度。类有两个方法来分别提高和降低当前的温度。
class Boiler:
def __init__(self):
self.temperature = 83 # 单位为摄氏度
def __str__(self):
return 'boiler temperature: {}'.format(self.temperature)
def increase_temperature(self, amount):
print("increasing the boiler's temperature by {} degrees".format(amount))
self.temperature += amount
def decrease_temperature(self, amount):
print("decreasing the boiler's temperature by {} degrees".format(amount))
self.temperature -= amount
下一步是添加语法,之前已学习过。我们也创建一个boiler实例,并输出其默认状态。
word = Word(alphanums)
command = Group(OneOrMore(word))
token = Suppress("->")
device = Group(OneOrMore(word))
argument = Group(OneOrMore(word))
event = command + token + device + Optional(token + argument)
boiler = Boiler()
print(boiler)
获取Pyparsing解析结果的最简单方式是使用parseString()方法,该方法返回的结果是一个ParseResults实例,它实际上是一个可视为嵌套列表的解析树。例如,执行print(event.parseString('increase -> boiler temperature -> 3 degrees'))得到的结果如下所示。
[['increase'], ['boiler', 'temperature'], ['3', 'degrees']]
因此,在这里,我们知道第一个子列表是命令(提高),第二个子列表是接收者(锅炉温度),
第三个子列表是参数( 3摄氏度)。实际上我们可以解开ParseResults实例,从而可以直接访问
事件的这三个部分。可直接访问意味着我们可以匹配模式找到应该执行哪个方法。
cmd, dev, arg = event.parseString('increase -> boiler temperature -> 3 degrees')
if 'increase' in ' '.join(cmd):
if 'boiler' in ' '.join(dev):
boiler.increase_temperature(int(arg[0]))
print(boiler)
执行上面的代码片段会得到以下输出。
boiler temperature: 83
increasing the boiler's temperature by 3 degrees
boiler temperature: 86
完整代码如下:
from pyparsing import Word, OneOrMore, Optional, Group, Suppress, alphanums
class Gate:
def __init__(self):
self.is_open = False
def __str__(self):
return 'open' if self.is_open else 'closed'
def open(self):
print('opening the gate')
self.is_open = True
def close(self):
print('closing the gate')
self.is_open = False
class Garage:
def __init__(self):
self.is_open = False
def __str__(self):
return 'open' if self.is_open else 'closed'
def open(self):
print('opening the garage')
self.is_open = True
def close(self):
print('closing the garage')
self.is_open = False
class Aircondition:
def __init__(self):
self.is_on = False
def __str__(self):
return 'on' if self.is_on else 'off'
def turn_on(self):
print('turning on the aircondition')
self.is_on = True
def turn_off(self):
print('turning off the aircondition')
self.is_on = False
class Heating:
def __init__(self):
self.is_on = False
def __str__(self):
return 'on' if self.is_on else 'off'
def turn_on(self):
print('turning on the heating')
self.is_on = True
def turn_off(self):
print('turning off the heating')
self.is_on = False
class Boiler:
def __init__(self):
self.temperature = 83# in celsius
def __str__(self):
return 'boiler temperature: {}'.format(self.temperature)
def increase_temperature(self, amount):
print("increasing the boiler's temperature by {} degrees".format(amount))
self.temperature += amount
def decrease_temperature(self, amount):
print("decreasing the boiler's temperature by {} degrees".format(amount))
self.temperature -= amount
class Fridge:
def __init__(self):
self.temperature = 2 # 单位为摄氏度
def __str__(self):
return 'fridge temperature: {}'.format(self.temperature)
def increase_temperature(self, amount):
print("increasing the fridge's temperature by {} degrees".format(amount))
self.temperature += amount
def decrease_temperature(self, amount):
print("decreasing the fridge's temperature by {} degrees".format(amount))
self.temperature -= amount
def main():
word = Word(alphanums)
command = Group(OneOrMore(word))
token = Suppress("->")
device = Group(OneOrMore(word))
argument = Group(OneOrMore(word))
event = command + token + device + Optional(token + argument)
gate = Gate()
garage = Garage()
airco = Aircondition()
heating = Heating()
boiler = Boiler()
fridge = Fridge()
tests = ('open -> gate',
'close -> garage',
'turn on -> aircondition',
'turn off -> heating',
'increase -> boiler temperature -> 5 degrees',
'decrease -> fridge temperature -> 2 degrees')
open_actions = {'gate':gate.open,
'garage':garage.open,
'aircondition':airco.turn_on,
'heating':heating.turn_on,
'boiler temperature':boiler.increase_temperature,
'fridge temperature':fridge.increase_temperature}
close_actions = {'gate':gate.close,
'garage':garage.close,
'aircondition':airco.turn_off,
'heating':heating.turn_off,
'boiler temperature':boiler.decrease_temperature,
'fridge temperature':fridge.decrease_temperature}
for t in tests:
if len(event.parseString(t)) == 2: # 没有参数
cmd, dev = event.parseString(t)
cmd_str, dev_str = ' '.join(cmd), ' '.join(dev)
if 'open' in cmd_str or 'turn on' in cmd_str:
open_actions[dev_str]()
elif 'close' in cmd_str or 'turn off' in cmd_str:
close_actions[dev_str]()
elif len(event.parseString(t)) == 3: # 有参数
cmd, dev, arg = event.parseString(t)
cmd_str, dev_str, arg_str = ' '.join(cmd), ' '.join(dev), ' '.join(arg)
num_arg = 0
try:
num_arg = int(arg_str.split()[0]) # 抽取数值部分
except ValueError as err:
print("expected number but got: '{}'".format(arg_str[0]))
if 'increase' in cmd_str and num_arg > 0:
open_actions[dev_str](num_arg)
elif 'decrease' in cmd_str and num_arg > 0:
close_actions[dev_str](num_arg)
if __neme__ == '__main__':
main()
输出如下:
opening the gate
closing the garage
turning on the aircondition
turning off the heating
increasing the boiler's temperature by 5 degrees
decreasing the fridge's temperature by 2 degrees
5. 小结
解释器模式用于为高级用户和领域专家提供一个类编程的框架,但没有暴露出编程语言那样的复杂性。这是通过实现一个DSL来达到目的的。
DSL是一种针对特定领域、表达能力有限的计算机语言。 DSL有两类,分别是内部DSL和外部DSL。内部DSL构建在一种宿主编程语言之上,依赖宿主编程语言,外部DSL则是从头实现,不依赖某种已有的编程语言。解释器模式仅与内部DSL相关。
乐谱是一个非软件DSL的例子。音乐演奏者像一个解释器那样,使用乐谱演奏出音乐。从软件的视角来看,许多Python模板引擎都使用了内部DSL。 PyT是一个高性能的生成(X)HTML的Python DSL。我们也看到Chromium的Mesa库是如何使用解释器模式将图形相关的C代码翻译成Python可执行对象的。
python设计模式之解释器模式的更多相关文章
- python设计模式之模板模式
python设计模式之模板模式 编写优秀代码的一个要素是避免冗余.在面向对象编程中,方法和函数是我们用来避免编写冗余代码的重要工具. 现实中,我们没法始终写出100%通用的代码.许多算法都有一些(但并 ...
- 乐在其中设计模式(C#) - 解释器模式(Interpreter Pattern)
原文:乐在其中设计模式(C#) - 解释器模式(Interpreter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 解释器模式(Interpreter Pattern) 作 ...
- Python设计模式之MVC模式
# -*- coding: utf-8 -*- # author:baoshan quotes = ('A man is not complete until he is married. Then ...
- C#设计模式:解释器模式(Interpreter Pattern)
一,C#设计模式:解释器模式(Interpreter Pattern) 1,解释器模式的应用场合是Interpreter模式应用中的难点,只有满足“业务规则频繁变化,且类似的模式不断重复出现,并且容易 ...
- 北风设计模式课程---解释器模式(Interpreter Pattern)
北风设计模式课程---解释器模式(Interpreter Pattern) 一.总结 一句话总结: 不仅要通过视频学,还要看别的博客里面的介绍,搜讲解,搜作用,搜实例 设计模式都是对生活的抽象,比如用 ...
- python设计模式之状态模式
python设计模式之状态模式 面向对象编程着力于在对象交互时改变它们的状态.在很多问题中,有限状态机(通常名为状态机)是一个非常方便的状态转换建模(并在必要时以数学方式形式化)工具.首先,什么是状态 ...
- python设计模式之命令模式
python设计模式之命令模式 现在多数应用都有撤销操作.虽然难以想象,但在很多年里,任何软件中确实都不存在撤销操作.撤销操作是在1974年引入的,但Fortran和Lisp分别早在1957年和195 ...
- python设计模式之外观模式
python设计模式之外观模式 系统会随着演化变得非常复杂,最终形成大量的(并且有时是令人迷惑的)类和交互,这种情况并不少见.许多情况下,我们并不想把这种复杂性暴露给客户端.外观设计模式有助于隐藏系统 ...
- python设计模式之原型模式
python设计模式之原型模式 对于原型模式而言,其中最主要的部分就是关于一个对象的复制,其中就包含两个方面:1.浅复制:2.深复制.具体的区别请看我相关的随笔.这里简略的说明一下,浅复制就等于对 ...
随机推荐
- 设计模式:command模式
目的:将命令设计成类的形式,并可以组织成队列 优点: 在需要的情况下,可以比较容易地将命令记入日志 可以容易的实现对请求的撤销和重做 由于新的具体命令类不影响其他的命令类,因此增加新的具体命令类很容易 ...
- python-在python3中使用容联云通讯发送短信验证码
容联云通讯是第三方平台,能够提供短信验证码和语音通信等功能,这里只测试使用短信验证码的功能,因此只需完成注册登录(无需实名认证等)即可使用其短信验证码免费测试服务,不过免费测试服务只能给控制台中指定的 ...
- liunx安装和部署nacos配置中心
1.下载https://github.com/alibaba/nacos/releases nacos-server-1.3.1.tar.gz 源码包2.上传到liunx服务器 /usr/lo ...
- 轻松应对并发问题,Newbe.Claptrap 框架中 State 和 Event 应该如何理解?
Newbe.Claptrap 框架中 State 和 Event 应该如何理解?最近整理了一下项目的术语表.今天就谈谈什么是 Event 和 State. Newbe.Claptrap 是一个用于轻松 ...
- PHP Cookie是什么
PHP Cookie cookie 常用于识别用户. Cookie 是什么? cookie 常用于识别用户.cookie 是一种服务器留在用户计算机上的小文件.每当同一台计算机通过浏览器请求页面时,这 ...
- PHP curl_multi_remove_handle函数
(PHP 5) curl_multi_remove_handle — 移除curl批处理句柄资源中的某个句柄资源 说明 int curl_multi_remove_handle ( resource ...
- 7.11 NOI模拟赛 graph 生成函数 dp 多项式
LINK:graph HDU题库里的原题 没做过自闭. 考虑dp 设\(f_{i,j}\)表示前i个点构成j个联通块是树的方案数. 对于一次询问答案即为\(\sum_{j}f_{n,j}j^k\) 考 ...
- Codeforces Round #649 (Div. 2) E. X-OR 交互 二进制 随机 期望
LINK:X-OR 本来是应该昨天晚上发的 可是昨天晚上 做这道题 写了一个分治做法 一直wa 然后查错 查不出来 心态崩了 想写对拍 发现交互库自己写不出来. 一系列sb操作 == 我都醉了. 今天 ...
- bzoj 1738 [Usaco2005 mar]Ombrophobic Bovines 发抖的牛 最大流+二分
题目要求所有牛都去避雨的最长时间最小. 显然需要二分 二分之后考虑如何判定. 显然每头牛都可以去某个地方 但是前提是最短路径<=mid. 依靠二分出来的东西建图.可以发现这是一个匹配问题 din ...
- idea中运行Tomcat后控制台出现乱码(统一设置成UTF-8)
出现问题:运行Tomcat后控制台出现乱码,输出语句乱码(idea 2019.3版本) 解决方法: 方案1:File----->Settings..----->Editor-----> ...