Week12-Lecture27-29
Lecture 27 Scheme Lists¶
Lab 08 Scheme¶
这个语法真的有点看的🤢,继续往下了。
其实主要作用是了解里面的基本语法,之后能用python实现它的解释器就OK了。
Lecture 28 Calculator¶
Reading 3.3 Exceptions¶
抛出异常(Raising)
- 异常本质上是一个对象,其类继承自
BaseException。 assert语句抛出的是AssertionError。- 更常见的做法是用
raise语句主动抛出一个异常实例,例如raise Exception('An error occurred')。 - 一旦异常被抛出且没被处理,当前代码块后续语句都不会执行,解释器会打印出调用栈信息(stack backtrace)后返回交互环境或终止程序。
3.3.1 异常对象¶
异常对象本身可以携带额外信息(属性),比如出错时的上下文数据。用户可以自定义异常类来实现这一点。
牛顿法例子:
- 定义一个继承自
Exception的新异常类IterImproveError,它在初始化时保存"最近一次的猜测值"last_guess。 - 修改
improve函数,在其内部捕获可能出现的ValueError(比如对负数开平方触发的 math domain error),然后包装成IterImproveError抛出,同时把当前猜测值一并带出。 find_zero函数再捕获这个IterImproveError,直接返回其中保存的last_guess,而不是让程序崩溃。
这样一来,即使牛顿法在迭代过程中因为浮点误差跑到定义域之外导致报错,程序也能返回一个"虽不精确但可用"的近似解,而不是直接抛错终止。
核心思想:异常机制让我们可以把"正常的迭代改进逻辑"和"错误处理逻辑"分离到不同代码块中,是程序模块化的又一种体现,后面实现解释器时也会用到这个技巧。
Reading 3.4 Interpreters for Languages with Combination¶
这一节讲的是如何用 Python 实现一个简单语言的解释器——这是"元语言抽象"(用一种语言去实现另一种语言)思想的具体体现。
3.4.1 A Scheme-Syntax Calculator¶
Calculator,只支持四则运算 + - * /。它沿用 Scheme 的调用表达式语法:
+和*可以接受任意数量的参数(0 个也行,分别返回单位元 0 和 1)-和/比较特殊:只传一个参数时表示取负/取倒数;传多个参数时表示第一个减去(除以)其余的和(积)
3.4.2 Expression Trees¶
要写解释器,就得把表达式当作数据来操作,而不只是概念上的树。这里用 Pair(类似 Scheme 的 cons pair)和空表 nil 来表示嵌套列表,Calculator 里所有的调用表达式本质上都是嵌套的 Scheme 列表,比如 (+ (* 3 4) 5) 会被表示成嵌套的 Pair 对象。
3.4.3 Parsing Expressions¶
解析分两步:
- 词法分析(tokenize):把输入字符串切分成最小语法单元(token),比如括号、符号、数字。
tokenize_line函数负责这一步。 - 语法分析(syntactic analysis):把 token 序列递归地组装成表达式树,由
scheme_read和辅助函数read_tail完成。因为一个表达式常常嵌套包含子表达式,所以这个过程是树递归的。
解析出错时会抛出带有描述信息的 SyntaxError。
3.4.4 Calculator Evaluation¶
核心是两个函数:
calc_eval(exp):对表达式求值。如果是数字,直接返回(这叫"自求值",self-evaluating);如果是Pair(调用表达式),先递归求出所有操作数的值,再调用calc_apply。calc_apply(operator, args):根据运算符字符串,用reduce等方式把对应的算术操作应用到参数列表上,并对参数个数不对的情况抛出TypeError。
这体现了经典的"按类型分派"(dispatch on type)结构:表达式的形式(数字 vs. 调用表达式)决定了求值方式。
REPL(读取-求值-打印循环):不断读取输入、解析、求值、打印结果,并通过 try/except 捕获 SyntaxError、TypeError、ZeroDivisionError 等异常,让用户输入错误后程序不会崩溃、还能继续交互,还支持 Ctrl-C / Ctrl-D 退出。