python设计模式–解释器模式(Interpreter Pattern)

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,再加“变量+上下文”、或“布尔与/或/非”,体会“文法→类、句子→树、解释→遍历”,这样对解释器模式会掌握得比较扎实。

发表评论