python实现解释器_Python设计模式之解释器模式

解释器模式

对每个应用来说,至少有以下两种不同的用户分类。

基本用户:这类用户只希望能够凭直觉使用应用。他们不喜欢花太多时间配置或学习应用的内部。对他们来说,基本的用法就足够了。

高级用户:这些用户,实际上通常是少数,不介意花费额外的时间学习如何使用应用的高级特性。如果知道学会之后能得到以下好处,他们甚至会去学习一种配置(或脚本)语言。

· 能够更好地控制一个应用

· 以更好的方式表达想法

· 提高生产力

解释器(Interpreter)模式仅能引起应用的高级用户的兴趣。这是因为解释器模式背后的主要思想是让非初级用户和领域专家使用一门简单的语言来表达想法。然而,什么是一种简单的语言?对于我们的需求来说,一种简单的语言就是没有像编程语言那么复杂的语言。

一般而言,我们想要创建的是一种领域特定语言(Domain Specific Language,DSL)。DSL是一种针对一个特定领域的有限表达能力的计算机语言。很多不同的事情都使用DSL,比如,战斗模拟、记账、可视化、配置、通信协议等。DSL分为内部DSL和外部DSL(请参考网页[t.cn/zHtEh5t]和网页[t.cn/hBfQ2Y])。

内部DSL构建在一种宿主编程语言之上。内部DSL的一个例子是,使用Python解决线性方程组的一种语言。使用内部DSL的优势是我们不必担心创建、编译及解析语法,因为这些已经被宿主语言解决掉了。劣势是会受限于宿主语言的特性。如果宿主语言不具备这些特性,构建一种表达能力强、简沽而且优美的内部DSL是富有挑战性的(请参考网页[t.cn/Rqr3B12])。

外部DSL不依赖某种宿主语言。DSL的创建者可以决定语言的方方面面(语法、旬法等),但也要负责为其创建一个解析器和编译器。为一种新语言创建解析器和编译器是一个非常复杂、长期而又痛苦的过程(请参考网页[t.cn/Rqr3B12])。

解释器模式仅与内部DSL相关。因此,我们的H标是使用宿主语言提供的特性构建一种简单但有用的语言,在这里,宿主语言是Python。注意,解释器根本不处理语言解析,它假设我们已经有某种便利形式的解析好的数据,可以是抽象语法树(abstract syntax tree,AST)或任何其他好用的数据结构(请参考[GOF95,第276页])。

现实生活的例子

音乐演奏者是现实中解释器模式的一个例子。五线谱图形化地表现了声音的音调和持续时间。音乐演奏者能根据五线谱的符号精确地重现声音。在某种意义上,五线谱是音乐的语言,音乐演奏者是这种语言的解释器。下图展示了音乐例子的图形化描述,经www.sourcemaking.com准许使用(请参考网页[t.cn/Rqr3Fqs])。

软件的例子

内部DSL在软件方面的例子有很多。PyT是一个用于生成(X)HTML的Python DSL。PyT关注性能,并声称能与Jinja2的速度相娘美(请参考网页[t.cn/Rqr1vlP])。当然,我们不能假定在PyT中必须使用解释器模式。然而,PyT是一种内部DSL,非常适合使用解释器模式。

Chromium是一个自由开源的浏览器软件,催生出了Google Chrome浏览器(请参考网页[t.cn/hMjLK])。Chromium的Mesa库Python绑定的一部分使用解释器模式将C样板参数翻译成Python对象并执行相关的命令(请参考网页[t.cn/Rqr1zZP])。

应用案例

在我们希望为领域专家和高级用户提供一种简单语言来解决他们的问题时,可以使用解释器模式。不过要强调的第一件事情是,解释器模式应仅用于实现简单的语言。如果语言具有外部DSL那样的要求,有更好的工具(yacc和lex、Bison、ANTLR等)来从头创建一种语言。

我们的目标是为专家提供恰当的编程抽象,使其生产力更高;这些专家通常不是程序员。理想情况下,他们使用我们的DSL并不需要了解高级Python知识,当然了解一点Python基础知识会更好,因为我们最终生成的是Python代码,但不应该要求了解Python高级概念。此外,DSL的性能通常不是一个重要的关注点。重点是提供一种语言,隐藏宿主语言的独特性,并提供人类更易读的语法。诚然,Python已经是一门可读性非常高的语言,与其他编程语言相比,其古怪的语法更少。

实现

我们来创建一种内部DSL控制一个智能屋。这个例子非常契合如今越来越受关注的物联网时代。用户能够使用一种非常简单的事件标记来控制他们的房子。一个事件的形式为command->receiver->arguments。参数部分是可选的。并不是所有事件都要求参数。不要求任何参数的事件例子如下所示。

open->gate

要求参数的事件例子如下所示:

increase -> boiler temperature -> 3 degrees

->符号用于标记事件一个部分的结束,并声明下一个部分的开始。实现一种内部DSL有多种方式。我们可以使用普通的正则表达式、字符串处理、操作符重载的组合以及元编程,或者一个能帮我们完成困难工作的库/工具。虽然在正规情况下,解释器不处理解析,但我觉得一个实战的例子也需要覆盖解析工作。因此,我决定使用一个工具来完成解析工作。该工具名为Pyparsing,是标准Python3发行版的一部分心。要想获取更多Pyparsing的信息,可参考Paul McGuire编写的迷你书Getting Started with Pyparsing。如果你的系统上还没安装Pyparsing,可以使用下面的命令来安装。

pip3 install pyparsing

下面的时序图展示了用户执行开门事件时发生的事情。对于其他事件,情况也是相似的,不过有些命令更复杂些,因为它们要求参数。

在编写代码之前,为我们的语言定义一种简单语法是一个好做法。我们可以使用巴科斯-诺尔形式(Backus-Naur Form,BNF)表示法来定义语法(请参考网页[t.cn/Rqr1ZrO])。

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)

执行上面的代码片段会得到以下输出。

python3 boiler.py

boiler temperature: 83

increasing the boiler's temperature by 3 degrees

boiler temperature: 86

interpreter.py的完整代码与我刚描述的没有什么大的不同,只是进行了扩展以支持更多的事件和设备。

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 # in celsius

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: # no argument

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: # argument

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]) # extract the numeric part

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 __name__ == '__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

执行上面的例子会得到以下输出。

python3 interpreter.py 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

如果你想针对这个例子进行更多的实验,我可以给你提一些建议。第一个会让例子更有意思的改变是让其变成交互式。目前,所有事件都是在tests元组中硬编码的。然而,用户希望能使用一个交互式提示符来激活命令。不要忘了Pyparsing对空格、Tab或意料之外的输出都是敏感的。例如,如果用户输入turn off -> heating 37,那会发生什么呢?

另一个可能的改进是,注意open_actions和close_actions映射是如何用于将一个接收者关联到一个方法的。使用一个映射而不是两个,可能吗?这样做有何优势?

小结

本章中,我们学习了解释器设计模式。解释器模式用于为高级用户和领域专家提供一个类编程的框架,但没有暴露出编程语言那样的复杂性。这是通过实现一个DSL来达到H的的。

DSL是一种针对特定领域、表达能力有限的计算机语言。DSL有两类,分别是内部DSL和外部DSL。内部DSL构建在一种宿主编程语言之上,依赖宿主编程语言,外部DSL则是从头实现,不依赖某种已有的编程语言。解释器模式仅与内部DSL相关。

乐谱是一个非软件DSL的例子。音乐演奏者像一个解释器那样,使用乐谱演奏出音乐。从软件的视角来看,许多Python模板引擎都使用了内部DSL。PyT是一个高性能的生成(X)HTML的 Python DSL。我们也看到Chromium的Mesa库是如何使用解释器模式将图形相关的C代码翻译成Python可执行对象的。

虽然一般来说解释器模式不处理解析的工作,但是在12.4节,我们使用了Pyparsing创建一种DSL来控制一个智能屋,并且看到使用一个好的解析工具以模式匹配来解释结果更加简单。

第13章将演示观察者模式。观察者模式用于在两个或多个对象之间创建一个发布—订阅通信类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/472471.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯 之 基础练习10:十进制转十六进制

【循环 整除 求余 判断】 /*问题描述 十六进制数是在程序设计时经常要使用到的一种整数的表示方式。它有0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F共16个符号,分别表示十进制数的0至15。十六进制的计数方法是满16进1,所以十进制数16在十六进制中是10,…

python选项卡控件_python GUI库图形界面开发之PyQt5选项卡控件QTabWidget详细使用方法与...

PyQt5选项卡控件QTabWidget简介QTabWidget控件提供了一个选项卡和一个页面区域,默认显示第一个选项卡的页面,通过单击各选项卡可以查看对应的界面,如果在一个窗口中显示的输入字段很多,则可以对这些字段进行拆分,分别放…

天池 在线编程 区间合并(字符串)

文章目录1. 题目2. 解题1. 题目 描述 现在给你两个字符串区间(按字典顺序), 请你判断两个区间是否可以合并。 字符串区间[a, b),包括所有以a开头的字符串。 例如,区间[a, b)和区间[ab,c)是可以合并的, 区间[a,b)和区间[b, c]也是可以合并的…

Android全局窗口模糊,javascript – 窗口焦点和模糊事件在Android浏览器上无法正常工作...

我发现当连接到窗口,文档或正文时,javascript焦点和模糊事件在Android浏览器上无法正确触发.我想要一个在桌面浏览器上正常工作的简单测试脚本,但在Android股票浏览器,Dolphin和Opera mobile上都失败了:Focus testwindow.onfocus function() {document.getElementB…

burp爆破线程设置多少_你知道线程池创建多少线程比较合理吗?

为什么会使用多线程创建多少线程比较合适结束语《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与源码解析 —— 精品合集》《Spring Boot …

LeetCode 1812. 判断国际象棋棋盘中一个格子的颜色

文章目录1. 题目2. 解题1. 题目 给你一个坐标 coordinates ,它是一个字符串,表示国际象棋棋盘中一个格子的坐标。下图是国际象棋棋盘示意图。 如果所给格子的颜色是白色,请你返回 true,如果是黑色,请返回 false 。 给…

Unity 官方网站

http://msdn.microsoft.com/en-us/library/dn170416.aspx转载于:https://www.cnblogs.com/Qiaoyq/p/4257355.html

python数组遍历输出所有组合_python遍历列表和数组实例讲解

python遍历实例总结python同时遍历数组的索引和值的实例你想在迭代一个序列的同时跟踪正在被处理的元素索引。获取索引内置的 enumerate() 函数可以很好的解决这个问题:>>> my_list [a, b, c]>>> for idx, val in enumerate(my_list):... print(…

dev c++ 代码补全_zsh配置与代码自动补全+tmux配置

先上链接ohmyzsh​github.comzsh-autosuggestions​github.comgpakosz/.tmux​github.com问题描述代码需要,更换了服务器,发现新服务器上zsh没有自动补全,难受得不行,并且没有个性化的配置,强迫症顶不住,tm…

android 获取栈顶activity,Android : 如何得到Activities栈顶的Activity名称

众所周知,Android中的任务等等都是通过栈来管理的,Activities的管理也不例外。栈这种数据结构是大家再熟悉不过了。它的先进后出特性让Android可以很容易实现从当前Activity回到或者重新启动先前的Activity.(注:当系统资源匮乏的时候,系统会释…

京东抢购助手_[Windows] 京东极速抢购助手V2.0,支持京东健康+扫货抢购

本软件仅供学习交流,完全免费,同时也为帮助有缘人能买到自用的口罩,度过这段特殊时期!请勿用于其它用途,谢谢!如有违规,请删帖!下载地址:https://www.lanzous.com/ia3bfl…

SQL Server 和 Oracle 以及 MySQL 有哪些区别?

SQL,在这里我理解成SQL Server。三者是目前市场占有率最高(依安装量而非收入)的关系数据库,而且很有代表性。排行第四的DB2(属IBM公司),与Oracle的定位和架构非常相似,就不赘述了。 …

LeetCode 1813. 句子相似性 III

文章目录1. 题目2. 解题1. 题目 一个句子是由一些单词与它们之间的单个空格组成,且句子的开头和结尾没有多余空格。 比方说,"Hello World" ,"HELLO" ,"hello world hello world" 都是句子。 每个单…

vim配置python开发环境_GitHub - TTWShell/legolas-vim: Vim配置,为python、go开发者打造的IDE。...

legolas-vim个人vim配置。支持python、go等自动提示,支持python、go的函数跳转(python支持虚拟环境)。 最终效果图(函数列表的feature已移除,因为大项目会导致性能问题):支持Python自动补全的最…

android 脚本引擎,GitHub - PassByYou888/zExpression: 脚本与编译器内部的语法引擎内核,也是一种op内核,zExpression可以轻松实现自己的脚本引擎...

zExpression 句法编译器解释器,脚本引擎内核技术体系解释:在编译原理的技术体系中,凡是处理文本化的代码前,都需要做一次预处理,其中我们常说的语法,语法糖,都是一种预处理程序词法:…

平面设计中的网格系统pdf_平面设计基础知识

导语:“ 给大家推荐优质书籍,包含平面设计基础入门知识的书籍分享”。平面设计基础知识分享书籍名称:《写给大家看的设计书》作者:[美]罗宾威廉姆斯著平面设计的四个原则:复杂的设计原理在《写给大家看的设计书》中凝炼…

LeetCode 1814. 统计一个数组中好对子的数目(哈希)

文章目录1. 题目2. 解题1. 题目 给你一个数组 nums &#xff0c;数组中只包含非负整数。 定义 rev(x) 的值为将整数 x 各个数字位反转得到的结果。 比方说 rev(123) 321 &#xff0c; rev(120) 21 。我们称满足下面条件的下标对 (i, j) 是 好的 &#xff1a; 0 < i <…

如何看当前windows是utf8还是gbk_监理工程师5月份出教材,现在如何备考?

监理工程师教材预计5月份出版&#xff0c;相信考生都知道监理工程师教材对于考生复习的重要性&#xff0c;那么现在考生应该如何学习呢?听小编给你说一说。教材没有发布之前怎么学习?教材改动比较大&#xff0c;没有发布之前我们就不要学习了么?小编不这么认为&#xff0c;监…

巧用Hint

一般计算fibonacci的方法&#xff1a; 1 def fibonacci (n): 2 if n 0 or n 1: 3 return 1 4 else: 5 return fibonacci(n-1) fibonacci(n-2) 这样的 call graph for fibonacci with n4: 当计算 fibonacci(30)的时候还可以&#xff0c;当计算 fibon…

LeetCode 1816. 截断句子

文章目录1. 题目2. 解题1. 题目 句子 是一个单词列表&#xff0c;列表中的单词之间用单个空格隔开&#xff0c;且不存在前导或尾随空格。每个单词仅由大小写英文字母组成&#xff08;不含标点符号&#xff09;。 例如&#xff0c;"Hello World"、"HELLO"…