20-手写一个迷你Python解释器-500行实现词法分析到字节码执行

20-手写一个迷你Python解释器-500行实现词法分析到字节码执行 文章目录手写一个迷你 Python 解释器——500 行实现词法分析 → 语法分析 → 字节码执行导入语1 ~ 解释器的三层翻译——整体架构2 ~ 第一步词法分析器 Lexer——字符 → Token2.1 Token 类型定义2.2 Lexer 实现2.3 测试3 ~ 第二步语法分析器 Parser——Token → AST3.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(varx,exprBinOp(left2,op,rightBinOp(left3,op*,right4)))↓ 执行:2(3*4)21214→ x142 ~ 第一步词法分析器 Lexer——字符 → Token2.1 Token 类型定义# 我们的迷你解释器支持的 Token 类型classTokenType:NUMBERNUMBER# 整数PLUSPLUS# MINUSMINUS# -STARSTAR# *SLASHSLASH# /LPARENLPAREN# (RPARENRPAREN# )ASSIGNASSIGN# NAMENAME# 变量名IFIFWHILEWHILEEQEQ# LTLT# GTGT# SEMISEMI# ;NEWLINENEWLINEEOFEOF# 输入结束2.2 Lexer 实现classLexer:def__init__(self,source):self.sourcesource self.pos0self.tokens[]deftokenize(self):whileself.poslen(self.source):chself.source[self.pos]# 跳过空格ifch.isspace():self.pos1continue# 数字ifch.isdigit():numwhileself.poslen(self.source)andself.source[self.pos].isdigit():numself.source[self.pos]self.pos1self.tokens.append((NUMBER,int(num)))continue# 标识符和保留字ifch.isalpha():identwhileself.poslen(self.source)andself.source[self.pos].isalpha():identself.source[self.pos]self.pos1# 保留字ifidentif:self.tokens.append((IF,ident))elifidentwhile:self.tokens.append((WHILE,ident))else:self.tokens.append((NAME,ident))continue# 运算符ifch:ifself.peek():self.tokens.append((EQ,))self.pos2else:self.tokens.append((ASSIGN,))self.pos1continueifch:self.tokens.append((PLUS,));self.pos1;continueifch-:self.tokens.append((MINUS,-));self.pos1;continueifch*:self.tokens.append((STAR,*));self.pos1;continueifch/:self.tokens.append((SLASH,/));self.pos1;continueifch(:self.tokens.append((LPAREN,());self.pos1;continueifch):self.tokens.append((RPAREN,)));self.pos1;continueifch:self.tokens.append((LT,));self.pos1;continueifch:self.tokens.append((GT,));self.pos1;continueifch;:self.tokens.append((SEMI,;));self.pos1;continueraiseSyntaxError(f不认识的字符:{ch})self.tokens.append((EOF,None))returnself.tokensdefpeek(self):ifself.pos1len(self.source):returnself.source[self.pos1]returnNone2.3 测试lexerLexer(x 2 3 * 4)tokenslexer.tokenize()fortintokens:print(t)# 输出# (NAME, x)# (ASSIGN, )# (NUMBER, 2)# (PLUS, )# (NUMBER, 3)# (STAR, *)# (NUMBER, 4)# (EOF, None)3 ~ 第二步语法分析器 Parser——Token → AST3.1 递归下降解析classParser:def__init__(self,tokens):self.tokenstokens self.pos0defpeek(self):returnself.tokens[self.pos]defconsume(self,expected_typeNone):tokenself.tokens[self.pos]ifexpected_typeandtoken[0]!expected_type:raiseSyntaxError(f期望{expected_type}找到{token})self.pos1returntokendefparse_expression(self):expression term (( | -) term)*leftself.parse_term()whileself.peek()[0]in(PLUS,MINUS):opself.consume()[0]rightself.parse_term()left(binop,op,left,right)returnleftdefparse_term(self):term factor ((* | /) factor)*leftself.parse_factor()whileself.peek()[0]in(STAR,SLASH):opself.consume()[0]rightself.parse_factor()left(binop,op,left,right)returnleftdefparse_factor(self):factor NUMBER | NAME | ( expression )tokenself.peek()iftoken[0]NUMBER:self.consume()return(number,token[1])eliftoken[0]NAME:self.consume()return(var,token[1])eliftoken[0]LPAREN:self.consume()exprself.parse_expression()self.consume(RPAREN)returnexprraiseSyntaxError(f意外的 token:{token})defparse_statement(self):tokenself.peek()iftoken[0]NAME:name_tokenself.consume()ifself.peek()[0]ASSIGN:self.consume()exprself.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())returnstatements3.2 测试tokensLexer(x 2 3 * 4).tokenize()astParser(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,rightnode[1],node[2],node[3]lvalself.eval(left)rvalself.eval(right)ifopPLUS:returnlvalrvalifopMINUS:returnlval-rvalifopSTAR:returnlval*rvalifopSLASH:returnlval/rvalelifnode[0]assign:var_name,exprnode[1],node[2]valueself.eval(expr)self.variables[var_name]valuereturnvalueelifnode[0]expr:returnself.eval(node[1])raiseRuntimeError(f未知节点类型:{node})defrun(self,source):tokensLexer(source).tokenize()astParser(tokens).parse()forstmtinast:resultself.eval(stmt)returnresult,self.variables4.3 完整运行evaluatorEvaluator()result,variablesevaluator.run(x 2 3 * 4)print(fx {variables[x]})# 输出x 14result,variablesevaluator.run(y x * 2)print(fy {variables[y]})# 输出y 285 ~ 这个迷你解释器和真正的 CPython 差在哪我们的迷你版本CPython词法手写 Lexertokenize.c——基于状态机的复杂 lexer语法递归下降 ParserParser/parser.c——自顶向下 LL 分析AST简单的元组表示丰富的 AST 节点类ast.Module等代码生成❌ 没有将 AST 编译为字节码compile.c执行直接求值 AST用栈机执行字节码ceval.c优化❌ 无JIT 编译器、类型推断等但核心的三层翻译架构完全一样——任何解释器都是按这个模式工作。这也解释了我们前两篇文章为什么能深入讨论字节码和帧对象——这些组件的存在是因为 CPython 有一个真实的编译器和虚拟机。思考 总结迷你解释器告诉我们三件事解释器没有魔法——它只是三层翻译器字符 → Token → AST → 结果。每一层各自独立各自做一次翻译。递归下降解析器非常适合入门——每个语法规则是一个函数直接映射到数学定义的语法公式。500 行 Python 足够实现一个变量的计算器。扩展它支持 if/while/函数定义/函数调用就是逐步走向真正的解释器。这个迷你解释器的代码体现了基础但正确的解释器架构——正是 CPython 架构的微缩版。结尾第二板块源码拆解十篇全部完结。感谢各位一路读来源码骑士 — 源码级拆解从底层看透技术关注跟博主一起从源码视角深耕底层原理❤️点赞让优质内容被更多人看见⭐收藏核心知识点存好随用随查评论分享你的经验或疑问一起交流一键四连别忘了给博主一键四连️寄语写一个解释器是理解一个语言的最快方法。结语从 GIL 到列表扩容公式、从 copy 模块源码到__slots__描述符、再到生成器的帧暂停、最后手写解释器——第二板块的目标是让你看着 Python 代码时脑子里有一张 C 层的内存图。第三板块进入工程落地——Django、Docker部署、性能优化——不见不散一键四连