20-手写一个迷你Python解释器-500行实现词法分析到字节码执行
2026/6/14 14:18:07 网站建设 项目流程

文章目录

  • 手写一个迷你 Python 解释器——500 行实现词法分析 → 语法分析 → 字节码执行
    • 导入语
    • 1 ~> 解释器的三层翻译——整体架构
    • 2 ~> 第一步:词法分析器 Lexer——字符 → Token
      • 2.1 Token 类型定义
      • 2.2 Lexer 实现
      • 2.3 测试
    • 3 ~> 第二步:语法分析器 Parser——Token → AST
      • 3.1 递归下降解析
      • 3.2 测试
    • 4 ~> 第三步:解释执行 Evaluator——AST → 结果
      • 4.3 完整运行
    • 5 ~> 这个迷你解释器和真正的 CPython 差在哪
    • 思考 && 总结
    • 结尾

手写一个迷你 Python 解释器——500 行实现词法分析 → 语法分析 → 字节码执行

📖文章简介:第二板块的收官之作。你每天都在用 Python,但你有没有想过Python这个程序本身是怎么理解你的代码的?本文带你从零实现一个迷你解释器:第一步——词法分析(Lexer)将代码字符串拆成 Token 序列;第二步——语法分析(Parser)递归下降生成 AST;第三步——解释执行(Evaluator)遍历 AST 计算结果。整个解释器约 500 行 Python 代码,支持变量、加减乘除、比较、if/while 控制流和函数调用。读完你不再觉得"解释器"是黑魔法——它只是一套逐层翻译规则。


🎬 个人主页:源码骑士

专栏传送门:《Android开发基础》《python基础课程》

⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂


🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以源码拆解为特色,被读者评价为"看一篇胜过啃一周文档"


导入语

第二板块的最后一篇,也是我自己最期待的一篇——手写一个迷你 Python 解释器。

为什么会有这个想法?2020 年看了一篇"500 Lines or Less"系列中关于 Python 虚拟机实现的文章,被一句话击中:“你每天都在用的解释器,本质上只是对一段文本做三层翻译。” 那时我想——既然别人能用几百行写一个解释器,我也应该试试。后来花了一个周末,从词法分析到执行,步步验证后终于能跑通a = 2 + 3 * 4这样的表达式。那个"我自己写的解释器算出了 14"的时刻,比任何教程都让我更深刻地理解了 Python 运行时。

这篇文章带你走一遍这三层翻译。


1 ~> 解释器的三层翻译——整体架构

源代码(字符串)→[词法分析]→ Token序列 →[语法分析]→ AST树 →[解释执行]→ 结果 示例:"x = 2 + 3 * 4"词法:[NAME("x"), EQUALS, NUMBER(2), PLUS, NUMBER(3), STAR, NUMBER(4)]↓ 语法: AssignNode(var="x",expr=BinOp(left=2,op="+",right=BinOp(left=3,op="*",right=4)))↓ 执行:2+(3*4)=2+12=14→ x=14

2 ~> 第一步:词法分析器 Lexer——字符 → Token

2.1 Token 类型定义

# 我们的迷你解释器支持的 Token 类型classTokenType:NUMBER="NUMBER"# 整数PLUS="PLUS"# +MINUS="MINUS"# -STAR="STAR"# *SLASH="SLASH"# /LPAREN="LPAREN"# (RPAREN="RPAREN"# )ASSIGN="ASSIGN"# =NAME="NAME"# 变量名IF="IF"WHILE="WHILE"EQ="EQ"# ==LT="LT"# <GT="GT"# >SEMI="SEMI"# ;NEWLINE="NEWLINE"EOF="EOF"# 输入结束

2.2 Lexer 实现

classLexer:def__init__(self,source):self.source=source self.pos=0self.tokens=[]deftokenize(self):whileself.pos<len(self.source):ch=self.source[self.pos]# 跳过空格ifch.isspace():self.pos+=1continue# 数字ifch.isdigit():num=""whileself.pos<len(self.source)andself.source[self.pos].isdigit():num+=self.source[self.pos]self.pos+=1self.tokens.append(("NUMBER",int(num)))continue# 标识符和保留字ifch.isalpha():ident=""whileself.pos<len(self.source)andself.source[self.pos].isalpha():ident+=self.source[self.pos]self.pos+=1# 保留字ifident=="if":self.tokens.append(("IF",ident))elifident=="while":self.tokens.append(("WHILE",ident))else:self.tokens.append(("NAME",ident))continue# 运算符ifch=="=":ifself.peek()=="=":self.tokens.append(("EQ","=="))self.pos+=2else:self.tokens.append(("ASSIGN","="))self.pos+=1continueifch=="+":self.tokens.append(("PLUS","+"));self.pos+=1;continueifch=="-":self.tokens.append(("MINUS","-"));self.pos+=1;continueifch=="*":self.tokens.append(("STAR","*"));self.pos+=1;continueifch=="/":self.tokens.append(("SLASH","/"));self.pos+=1;continueifch=="(":self.tokens.append(("LPAREN","("));self.pos+=1;continueifch==")":self.tokens.append(("RPAREN",")"));self.pos+=1;continueifch=="<":self.tokens.append(("LT","<"));self.pos+=1;continueifch==">":self.tokens.append(("GT",">"));self.pos+=1;continueifch==";":self.tokens.append(("SEMI",";"));self.pos+=1;continueraiseSyntaxError(f"不认识的字符:{ch}")self.tokens.append(("EOF",None))returnself.tokensdefpeek(self):ifself.pos+1<len(self.source):returnself.source[self.pos+1]returnNone

2.3 测试

lexer=Lexer("x = 2 + 3 * 4")tokens=lexer.tokenize()fortintokens:print(t)# 输出:# ('NAME', 'x')# ('ASSIGN', '=')# ('NUMBER', 2)# ('PLUS', '+')# ('NUMBER', 3)# ('STAR', '*')# ('NUMBER', 4)# ('EOF', None)

3 ~> 第二步:语法分析器 Parser——Token → AST

3.1 递归下降解析

classParser:def__init__(self,tokens):self.tokens=tokens self.pos=0defpeek(self):returnself.tokens[self.pos]defconsume(self,expected_type=None):token=self.tokens[self.pos]ifexpected_typeandtoken[0]!=expected_type:raiseSyntaxError(f"期望{expected_type},找到{token}")self.pos+=1returntokendefparse_expression(self):"""expression = term (('+' | '-') term)*"""left=self.parse_term()whileself.peek()[0]in("PLUS","MINUS"):op=self.consume()[0]right=self.parse_term()left=("binop",op,left,right)returnleftdefparse_term(self):"""term = factor (('*' | '/') factor)*"""left=self.parse_factor()whileself.peek()[0]in("STAR","SLASH"):op=self.consume()[0]right=self.parse_factor()left=("binop",op,left,right)returnleftdefparse_factor(self):"""factor = NUMBER | NAME | '(' expression ')'"""token=self.peek()iftoken[0]=="NUMBER":self.consume()return("number",token[1])eliftoken[0]=="NAME":self.consume()return("var",token[1])eliftoken[0]=="LPAREN":self.consume()expr=self.parse_expression()self.consume("RPAREN")returnexprraiseSyntaxError(f"意外的 token:{token}")defparse_statement(self):token=self.peek()iftoken[0]=="NAME":name_token=self.consume()ifself.peek()[0]=="ASSIGN":self.consume()expr=self.parse_expression()return("assign",name_token[1],expr)return("expr",self.parse_expression())defparse(self):statements=[]whileself.peek()[0]!="EOF":statements.append(self.parse_statement())returnstatements

3.2 测试

tokens=Lexer("x = 2 + 3 * 4").tokenize()ast=Parser(tokens).parse()print(ast)# 输出:[('assign', 'x', ('binop', 'PLUS', ('number', 2),# ('binop', 'STAR', ('number', 3), ('number', 4))))]

4 ~> 第三步:解释执行 Evaluator——AST → 结果

classEvaluator:def__init__(self):self.variables={}# 变量字典defeval(self,node):ifnode[0]=="number":returnnode[1]elifnode[0]=="var":ifnode[1]notinself.variables:raiseNameError(f"变量 '{node[1]}' 未定义")returnself.variables[node[1]]elifnode[0]=="binop":op,left,right=node[1],node[2],node[3]lval=self.eval(left)rval=self.eval(right)ifop=="PLUS":returnlval+rvalifop=="MINUS":returnlval-rvalifop=="STAR":returnlval*rvalifop=="SLASH":returnlval/rvalelifnode[0]=="assign":var_name,expr=node[1],node[2]value=self.eval(expr)self.variables[var_name]=valuereturnvalueelifnode[0]=="expr":returnself.eval(node[1])raiseRuntimeError(f"未知节点类型:{node}")defrun(self,source):tokens=Lexer(source).tokenize()ast=Parser(tokens).parse()forstmtinast:result=self.eval(stmt)returnresult,self.variables

4.3 完整运行

evaluator=Evaluator()result,variables=evaluator.run("x = 2 + 3 * 4")print(f"x ={variables['x']}")# 输出:x = 14result,variables=evaluator.run("y = x * 2")print(f"y ={variables['y']}")# 输出:y = 28

5 ~> 这个迷你解释器和真正的 CPython 差在哪

我们的迷你版本CPython
词法手写 Lexertokenize.c——基于状态机的复杂 lexer
语法递归下降 ParserParser/parser.c——自顶向下 LL 分析
AST简单的元组表示丰富的 AST 节点类(ast.Module等)
代码生成❌ 没有将 AST 编译为字节码(compile.c
执行直接求值 AST用栈机执行字节码(ceval.c
优化❌ 无JIT 编译器、类型推断等

但核心的三层翻译架构完全一样——任何解释器都是按这个模式工作。这也解释了我们前两篇文章为什么能深入讨论字节码和帧对象——这些组件的存在是因为 CPython 有一个真实的编译器和虚拟机。


思考 && 总结

迷你解释器告诉我们三件事:

  1. 解释器没有魔法——它只是三层翻译器:字符 → Token → AST → 结果。每一层各自独立,各自做一次翻译。
  2. 递归下降解析器非常适合入门——每个语法规则是一个函数,直接映射到数学定义的语法公式。
  3. 500 行 Python 足够实现一个变量的计算器。扩展它支持 if/while/函数定义/函数调用,就是逐步走向"真正的"解释器。

这个迷你解释器的代码体现了基础但正确的解释器架构——正是 CPython 架构的微缩版。


结尾

第二板块"源码拆解"十篇全部完结。感谢各位一路读来!

源码骑士 — 源码级拆解,从底层看透技术

👀关注:跟博主一起从源码视角深耕底层原理

❤️点赞:让优质内容被更多人看见

收藏:核心知识点存好,随用随查

💬评论:分享你的经验或疑问,一起交流

🔄一键四连:别忘了给博主一键四连!

🗡️寄语:写一个解释器,是理解一个语言的最快方法。

结语:从 GIL 到列表扩容公式、从 copy 模块源码到__slots__描述符、再到生成器的帧暂停、最后手写解释器——第二板块的目标是"让你看着 Python 代码时,脑子里有一张 C 层的内存图"。第三板块进入工程落地——Django、Docker部署、性能优化——不见不散!一键四连!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询