异常(Exceptions)

  • 抛出异常(raising an exception)是python特有的报错方式,其会自动中断程序并输出错误信息(可见官方文档)。当然,我们也可以在程序中加入raiseassert语句主动设置异常抛出。比如:
    >>> raise Exception('An error occurred')
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    Exception: An error occurred
    在IDLE中,抛出异常后解释器将返回到初始交互状态;而在运行代码时抛出异常则会直接中止程序。

处理异常(try…except)

  • 为了让代码在遇到异常后不会直接中止程序,我们可以设置tryexcept语句(类似条件分支语句),格式如下:
    try:
        <try suite>
    except <exception class> as <name>:
        <except suite>
  • 具体而言,程序会先执行try块内的<try suite>,如果遇到异常,except语句就会根据<exception class>处理对应的异常类。
  • 异常类的内容会绑定到<name>上(属于局部变量,一般不会用到),然后通过执行<except suite>处理异常。下面是一个具体的示例:
    >>> try:
    	    x = 1 / 0
        except ZeroDivisionError as e:
            print('handling a', type(e))
            x = 0
    handling a <class 'ZeroDivisionError'>
    >>> x
    0
    另外,当<try suite>中存在函数调用,且抛出异常,那么它就会立刻逐层向外找被包含的try和对应的except语句。也就是说,无论try里面套了多少层函数,只要异常没有在更里面被捕获,它最终都会回到当前tryexcept。比如:
    def f1():
        f2()
    
    def f2():
        f3()
    
    def f3():
        raise ValueError("Error f3")
    
    try:
        f1()
        print("blablabla")
    except ValueError:
        print("Error outer try")
    只会输出Error outer try

异常对象(Exception Object)

  • 由于异常本身也是一种对象,所以我们也可以使用面向对象的方法构造异常对象,以实现除了输出报错位置与信息之外的功能。
  • 我们以之前的牛顿法为例,尝试在出现异常时返回当前的最优估计值。我们建立一个新的异常类IterImproveError,继承自Exception(python内置异常类):
    >>> class IterImproveError(Exception):
            def __init__(self, last_guess):
                self.last_guess = last_guess
  • 然后将这个异常类加到通用函数improve中(与之前的定义略有区别,迭代结束的条件不是误差小到一定程度,而是精确求得零点/达到最大迭代次数/抛出异常):
    >>> def improve(update, done, guess=1, max_updates=1000):
        k = 0
        try:
            while not done(guess) and k < max_updates:
                guess = update(guess)
                k = k + 1
            return guess
        except ValueError:
            raise IterImproveError(guess)
  • 接着,重新编写find_zero函数,引入处理异常语句:
    >>> def find_zero(f, df, guess=1):
            def done(x):
                return f(x) == 0
            try:
                return improve(newton_update(f, df), done, guess)
            except IterImproveError as e:
                return e.last_guess
    其中newton_update定义和之前的完全一样。
  • 下面以一个会触发异常的例子进行测试(求函数2x2+x2x^2+\sqrt{x}的零点):
    >>> from math import sqrt
    >>> find_zero(lambda x: 2*x*x + sqrt(x),lambda x: 4*x + 1 / (2 * sqrt(x))
    -0.030214676328644885
    虽然没能达到精确解00,但是比之前输入后直接报错(负数开根号)好一些。
  • 另外,python将tryexcept分为两个模块,也有利于我们分别对二者进行调整,并将其融入对象系统。

编程语言

  • 在继续Scheme之前,先简单说明高级语言与机器语言/汇编语言的区别:
    • 机器语言/汇编语言直接通过硬件本身进行解释。具体而言,其通过执行CPU预设的特定指令组合实现,与硬件的内存地址有关,无法进行进一步抽象;
    • 而高级语言程序的语句和表达式可以通过另一个程序进行解释或通过编译转为另一种语言,因此提供了抽象的方法(如名称指代、定义函数与对象等)。这使得高级语言程序可以在不同机器、甚至不同系统上运行。
      • 当然,高级语言之间也有层次区分:比如C语言就比python语言更接近机器语言(C语言通过编译可直接转为机器语言,而python的解释器主要通过C语言构造)
      • 可以通过python的dis语句对python程序进行反汇编,查看其解释器实现的字节码。

编程语言的基本要素

  • 语法(syntax):合法的语句与表达式(作为语言的组成形式)
  • 语义(semantics):语句和表达式的评估方法/执行规则。

这两个要素要么通过规范(文档),要么通过构建解释器/编译器进行实现。

更多细节可以参考国内的计算机组成原理、编译原理或相关课程。