Python 设计模式——解释器模式(Interpreter Pattern)详解
本文面向零基础新手,从“语法与表达式树”到解释执行,从简单算术到布尔表达式,全方位讲解解释器模式。
一、什么是解释器模式?
1.1 通俗理解
解释器模式是一种行为型设计模式,核心思想是:
为一种“简单语言”定义文法(语法规则),并把句子表示成“表达式”对象的结构(通常是树形);然后通过遍历这些表达式对象,根据每个节点类型执行对应的“解释”逻辑,从而得到句子的含义或计算结果。也就是说:用面向对象的方式,把“语法规则”变成类,把“句子”变成对象组合,通过调用各对象的“解释”方法来执行。
可以这样类比:
- 不用解释器:要计算 “1+2*3″,你可能会写一堆 if-else 或正则去拆字符串、按优先级算。语法一复杂就难维护。
- 用解释器:先把 “1+2*3” 变成一棵“表达式树”:根是“加”,左子是数字 1,右子是“乘”;“乘”的左子是 2,右子是 3。计算时:根“加”先让右子“乘”解释得到 6,再 1+6=7。每个语法规则对应一种表达式类(如数字、加法、乘法),每个类会解释自己并可能调用子表达式的解释结果。
所以,解释器模式解决的是:当有一种需要解释执行的“小语言”或表达式(如简单算式、简单逻辑式、简单脚本语句)时,用类来表示语法和句子结构,用“解释”方法遍历执行,使语法扩展和维护更清晰。
1.2 为什么需要解释器模式?
场景一:简单表达式、公式
例如配置里允许写 “price * 0.9″、”a and b”;或游戏里简单条件 “hp > 50 and level >= 2″。若每次都手写字符串解析和求值,容易出错且难扩展。用解释器:把表达式拆成“表达式树”,每种运算一个类,解释时递归求值。
场景二:简单 DSL(领域专用语言)
例如“按键序列”:”W W S A” 表示上上下左;或“简单命令”:”move 10″、”turn left”。用解释器可以把每条规则做成类,句子变成对象组合,解释时按类型执行。
场景三:语法相对固定、规则不多
解释器模式适合文法简单、规则数量有限的场景。若语法非常复杂(如完整编程语言),一般用专业的“词法分析+语法分析+解释/编译”工具,而不是手写一堆解释器类;解释器模式更像“用对象结构表达语法并解释执行”的一种设计思路。
1.3 适用场景(什么时候用?)
| 场景 | 说明 |
|---|---|
| 简单表达式、公式求值 | 四则运算、简单逻辑表达式,文法简单。 |
| 简单 DSL、脚本片段 | 配置表达式、简单命令、按键序列等。 |
| 文法可表示为类层次、规则数有限 | 每条规则一种表达式类,便于扩展。 |
注意:文法复杂时(如完整编程语言)应使用解析器生成器或现成库,解释器模式更适合“小语言”。
简单记忆:有一种需要解释执行的简单语法,且希望用类表示规则、用对象组合表示句子时,可以考虑解释器模式。
二、解释器模式的结构(四个角色)
| 角色 | 说明 |
|---|---|
| 抽象表达式(AbstractExpression) | 定义“解释”接口,如 interpret(context) 或 eval(),返回解释结果。 |
| 终结符表达式(TerminalExpression) | 对应文法里的“叶子”(如数字、变量名),不再包含子表达式,直接返回一个值。 |
| 非终结符表达式(NonterminalExpression) | 对应文法里的“组合规则”(如加法、乘法),内部持有子表达式(一个或多个),解释时先解释子表达式,再根据规则组合结果。 |
| 上下文(Context) | 可选。存放解释过程中需要的信息(如变量表、输入流),在遍历表达式树时传递。 |
关系可以理解为:
- 句子被表示成由“表达式对象”组成的树:叶子是终结符(数字、变量),中间节点是非终结符(加、乘、与、或)。
- 解释时从根开始调用
interpret(),非终结符先对子节点调用interpret(),再把自己的规则 applied 到子结果上;终结符直接返回值。最终得到整句的解释结果。
三、示例一:简单算术(数字 + 加法 + 乘法)
需求:支持“数字”“加法”“乘法”三种表达式;句子如 “1+2*3” 先被建成表达式树,再解释得到 7。
3.1 抽象表达式 + 终结符(数字)
from abc import ABC, abstractmethod
class Expr(ABC):
"""抽象表达式:所有表达式都要能“求值”"""
@abstractmethod
def eval(self) -> int:
pass
class Number(Expr):
"""终结符表达式:就是一个数字"""
def __init__(self, value: int):
self.value = value
def eval(self) -> int:
return self.value
3.2 非终结符(加法、乘法)
class Add(Expr):
"""非终结符:加法,有两个子表达式"""
def __init__(self, left: Expr, right: Expr):
self.left = left
self.right = right
def eval(self) -> int:
return self.left.eval() + self.right.eval()
class Mul(Expr):
"""非终结符:乘法"""
def __init__(self, left: Expr, right: Expr):
self.left = left
self.right = right
def eval(self) -> int:
return self.left.eval() * self.right.eval()
3.3 手动建树并解释(对应 1+2*3)
# 1 + 2*3 的表达式树:
# Add
# /
# 1 Mul
# /
# 2 3
tree = Add(Number(1), Mul(Number(2), Number(3)))
print(tree.eval()) # 7
要点:这里还没有“从字符串解析出树”(那属于词法/语法分析);解释器模式负责的是用对象表示句子结构 + 用 eval() 解释。建树可以手写,也可以用简单解析器生成。
四、示例二:带上下文的简单“变量”
需求:表达式里可以有“变量”(如 x、y),解释时从上下文(Context)里取变量的值。
4.1 上下文
class Context:
"""上下文:存变量名 -> 值"""
def __init__(self):
self.vars = {}
def set(self, name: str, value: int):
self.vars[name] = value
def get(self, name: str) -> int:
return self.vars.get(name, 0)
4.2 变量(终结符)+ 加法(非终结符,传 context)
class Expr(ABC):
@abstractmethod
def eval(self, ctx: Context) -> int:
pass
class Number(Expr):
def __init__(self, value: int):
self.value = value
def eval(self, ctx: Context) -> int:
return self.value
class Variable(Expr):
"""终结符:变量,从上下文取值"""
def __init__(self, name: str):
self.name = name
def eval(self, ctx: Context) -> int:
return ctx.get(self.name)
class Add(Expr):
def __init__(self, left: Expr, right: Expr):
self.left = left
self.right = right
def eval(self, ctx: Context) -> int:
return self.left.eval(ctx) + self.right.eval(ctx)
4.3 使用
ctx = Context()
ctx.set("x", 10)
ctx.set("y", 2)
# x + y
expr = Add(Variable("x"), Variable("y"))
print(expr.eval(ctx)) # 12
五、示例三:简单布尔表达式(与、或、非)
需求:支持 True/False(终结符)和 and、or、not(非终结符),解释得到布尔值。
class BoolExpr(ABC):
@abstractmethod
def eval(self) -> bool:
pass
class TrueLiteral(BoolExpr):
def eval(self) -> bool:
return True
class FalseLiteral(BoolExpr):
def eval(self) -> bool:
return False
class And(BoolExpr):
def __init__(self, left: BoolExpr, right: BoolExpr):
self.left = left
self.right = right
def eval(self) -> bool:
return self.left.eval() and self.right.eval()
class Or(BoolExpr):
def __init__(self, left: BoolExpr, right: BoolExpr):
self.left = left
self.right = right
def eval(self) -> bool:
return self.left.eval() or self.right.eval()
class Not(BoolExpr):
def __init__(self, expr: BoolExpr):
self.expr = expr
def eval(self) -> bool:
return not self.expr.eval()
使用
# (True and False) or True
expr = Or(And(TrueLiteral(), FalseLiteral()), TrueLiteral())
print(expr.eval()) # True
六、示例四:简单“命令”解释(按键序列)
需求:语言只有两种“句子”:单个按键 “W”/”S”/”A”/”D”,或“序列”即多个句子依次执行。解释结果是执行动作(这里用打印模拟)。
6.1 上下文(可选:记录当前状态)
class GameContext:
def __init__(self):
self.x = 0
self.y = 0
6.2 表达式:按键(终结符)+ 序列(非终结符)
class Command(ABC):
@abstractmethod
def interpret(self, ctx: GameContext):
pass
class Key(Command):
"""终结符:单个按键"""
def __init__(self, key: str):
self.key = key
def interpret(self, ctx: GameContext):
if self.key == "W":
ctx.y += 1
print(" 向上")
elif self.key == "S":
ctx.y -= 1
print(" 向下")
elif self.key == "A":
ctx.x -= 1
print(" 向左")
elif self.key == "D":
ctx.x += 1
print(" 向右")
class Sequence(Command):
"""非终结符:按顺序执行多个命令"""
def __init__(self, commands: list):
self.commands = commands
def interpret(self, ctx: GameContext):
for cmd in self.commands:
cmd.interpret(ctx)
6.3 使用
ctx = GameContext()
# W W S 表示:上、上、下
seq = Sequence([Key("W"), Key("W"), Key("S")])
seq.interpret(ctx)
# 向上 / 向上 / 向下
print(ctx.x, ctx.y) # 0 1
七、解释器模式 vs 其他
| 模式 | 目的 | 与解释器的区别 |
|---|---|---|
| 解释器 | 用类表示语法、用对象组合表示句子并解释执行 | 强调“文法→类”“句子→对象树”“解释=遍历求值”。 |
| 组合 | 树形结构、统一接口、递归操作 | 组合不关心“语法/解释”,只关心部分-整体;解释器用树表达语法并解释。 |
| 访问者 | 对复杂结构做多种操作 | 若对表达式树要做多种遍历(求值、打印、优化),可配合访问者;解释器侧重“一种解释”。 |
简单记忆:解释器 = 语法规则→类 + 句子→表达式树 + 解释=对树做 eval/interpret。
八、常见问题与注意点
8.1 谁负责“从字符串到树”?
解释器模式只负责“用对象表示句子 + 解释执行”。从字符串(如 “1+2*3″)生成表达式树,属于词法分析 + 语法分析;可以手写简单解析器,或对复杂文法用工具(如 PLY、ast)。本文示例中树是手建的,便于理解“解释”部分。
8.2 文法复杂时怎么办?
若规则很多、递归复杂,用解释器模式会类爆炸、难维护。此时应使用解析器生成器或现成库做“解析”,得到 AST 后再遍历执行;解释器模式更适合小语言、规则少的场景。
8.3 上下文的作用
上下文用来在解释过程中传递全局或共享信息(如变量表、输入流、当前环境)。终结符和非终结符的 interpret 都可以接收 context,按需读写。
九、小结
- 解释器模式:为一种简单语言定义文法,用类表示语法规则(终结符、非终结符),用对象组合表示句子(表达式树);通过解释方法(如 eval、interpret)遍历树并执行,得到结果。
- 四个角色:抽象表达式、终结符表达式、非终结符表达式、上下文(可选)。
- 典型用途:简单算术、简单布尔表达式、简单 DSL、配置表达式等“小语言”的解释执行。
- 实现要点:每种语法规则对应一个表达式类;终结符直接返回值,非终结符先解释子表达式再组合结果;可配合上下文传递变量等。
建议先写“数字+加法+乘法”的表达式树与 eval,再加“变量+上下文”、或“布尔与/或/非”,体会“文法→类、句子→树、解释→遍历”,这样对解释器模式会掌握得比较扎实。