解释器模式练习——表达式树、上下文与布尔表达式
按《Python 设计模式——解释器模式》的建议,通过三道练习巩固:① 简单算术表达式树(数字、加、乘、减);② 带上下文的变量表达式;③ 布尔表达式(与、或、非)。每步都有完整可运行代码和验证要点。
练习一:简单算术表达式树(数字、加、乘、减)
目的
体会文法→类、句子→树、解释→eval 递归:每种运算一种类,表达式是对象组合成的树,求值就是对根调用 eval()。
要求
- 实现:数字(终结符)、加法、乘法、减法(非终结符)。
- 手动建一棵树,表示
10 - 2 * 3(即 10 – (2*3) = 4),并调用eval()得到 4。
参考答案
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
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()
class Sub(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()
# 10 - 2*3 的树:Sub(Number(10), Mul(Number(2), Number(3)))
tree = Sub(Number(10), Mul(Number(2), Number(3)))
print(tree.eval()) # 应为 4
验证要点
tree.eval()输出 4。- 理解:Sub 的右子先 eval 得到 6,再 10-6=4;乘法优先是由树的结构决定的(乘在下面),不是由 eval 里的顺序决定的。
可选
再建一棵树表示 (1+2) * 3,即 Mul(Add(Number(1), Number(2)), Number(3)),验证 eval() 为 9。
练习二:带上下文的变量表达式
目的
在算术表达式基础上加变量(终结符),解释时从上下文取变量值;体会“上下文在整棵树解释过程中传递”。
要求
- 实现 Context:支持
set(name, value)、get(name)。 - 实现 Variable(终结符):
eval(ctx)时返回ctx.get(变量名)。 - 修改 Number、Add、Mul 的
eval为eval(self, ctx: Context),并在递归时把ctx传给子节点。 - 建树表示 *x 2 + 1,在上下文中设
x=5,求值得到 11**。
参考答案
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)
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)
class Mul(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)
# x * 2 + 1,即 Add(Mul(Variable("x"), Number(2)), Number(1))
ctx = Context()
ctx.set("x", 5)
tree = Add(Mul(Variable("x"), Number(2)), Number(1))
print(tree.eval(ctx)) # 应为 11
验证要点
ctx.set("x", 5)后,tree.eval(ctx)为 11(5*2+1)。- 修改
ctx.set("x", 10),同一棵树应得到 21,说明“句子”是树,“解释”依赖上下文。
练习三:布尔表达式(与、或、非)
目的
用解释器模式表示布尔表达式:终结符为 True/False,非终结符为 And、Or、Not;对根调用 eval() 得到布尔值。
要求
- 实现 TrueLiteral、FalseLiteral(终结符)。
- 实现 And、Or、Not(非终结符,可只做二元 And/Or 和一元 Not)。
- 建树表示 not (False and True),即 Not(And(FalseLiteral(), TrueLiteral())),求值应为 True。
参考答案
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()
# not (False and True) -> not(And(False, True)) -> True
tree = Not(And(FalseLiteral(), TrueLiteral()))
print(tree.eval()) # True
验证要点
tree.eval()为 True(False and True = False,not False = True)。- 可再建一棵 (True or False) and Not(False),验证为 True。
三步汇总与自检
| 练习 | 重点 | 关键点 |
|---|---|---|
| 一 | 算术表达式树 | 终结符 Number,非终结符 Add/Mul/Sub;树结构决定运算优先级。 |
| 二 | 变量 + 上下文 | Variable 从 Context 取值;eval 沿树传递 ctx。 |
| 三 | 布尔表达式 | 终结符 True/False,非终结符 And/Or/Not;同一套“解释=递归 eval”。 |
自检问题
-
终结符和非终结符在解释时有什么区别?
终结符直接返回值(或从上下文取值),不依赖子表达式;非终结符先对子节点 eval,再按规则组合结果。 -
上下文的作用是什么?
在整棵树的解释过程中传递共享信息(如变量表),供终结符(如 Variable)或非终结符按需读写。 -
为什么说“运算优先级由树的结构决定”?
谁在树的更下层,谁先被递归到;例如Sub(10, Mul(2,3))里乘在下面,先算 2*3,再算 10-6。
做完以上三道练习,再对照文档中的示例,对解释器模式会掌握得比较扎实。建议每道题先自己写一遍,再对照参考答案和验证要点检查。