Python 中的异常 (Exception)

以下 Python 版本为 Python 3.8.10 .

初探异常

错误与异常

错误:

  • 语法错误 .
  • 逻辑错误 .

异常:程序运行过程中,出现的意料之外的错误(大概类似 corner case),例如除 0(ZeroDivisionError),尾迭代器自增(StopIteration),解包一个 int 等(TypeError),其他异常见「常见异常」.

很明了吧 .

Traceback

我们平时写 Python 码写出 bug 则解释器会报错(运行时),这个就是 Traceback .

处理用户未处理的异常的方法就是先终止程序,再通过 Traceback(堆栈回溯,也称向后追踪)来显示异常发生的上下文 .

我们可以通过引用 traceback 模块来访问 Traceback .

Python 所有异常

Python 异常 解释
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入 ^C
Exception 常规错误的基类
StopIteration 迭代器越界
GeneratorExit 生成器异常于是通知退出
SystemExit Python 解释器请求退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制(上溢)
ZeroDivisionError 除 / 取模零(所有数据类型)
AssertionError 断言 (assert) 异常
AttributeError 对象没有这个属性
EOFError 输入读到 EOF
EnvironmentError 操作系统错误的基类
IOError 输入 / 输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列 (list) 中找不到要调用的索引
KeyError 映射 (map) 中找不到要调用的键
MemoryError 内存溢出
NameError 未声明 / 初始化对象
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 调用没有的方法
SyntaxError 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数(类型不对)
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 所有警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型 (long) 的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告

异常处理

try... 捕获异常

这个可以类比 C++ 中的 try ... catch,不过 Python 异常更灵活一点(因为解释性甚至连 C++ 中一些引发编译错误 (Compile Error, CE) 的内容都能补救回来)

平凡的处理方法是 try ... except

try:
    代码
except 错误类型A as 接受错误信息的变量A:
    处理代码A
except 错误类型B as 接受错误信息的变量B:
    处理代码B
...

except 里面啥都不填就是自动捕获所有异常 .

接受处理的变量,设为 e,则可以做:

  • str(e) / e.message,只给出异常信息的字符串,不包括异常信息的类型 .

  • repr(e),给出较全的异常信息,包括异常信息的类型 .

  • 使用 traceback 模块,此时获取的信息最全,与 Python 解释器运行程序出现错误信息一致,下面是两个常用方法:

    • traceback.print_exc() 打印异常信息到标准错误流 (stderr) .
    • traceback.format_exc() 将同样的输出获取为字符串 .

    其余用法请查阅文档 .

  • 使用 sys 模块:sys.exc_info() 方法可以获取正在处理的异常信息,即 except 子句正在处理的异常,其返回值为一个 tuple 类型的三元组 (exc_type, exc_value, exc_traceback),其中,exc_type 为获取到的异常类型;exc_value 为该异常类型对象;exc_traceback 为一个 traceback 对象,包含异常最初发生的调用栈信息 .
    例子:

a = 10
b = 0
try:
    c = a / b
    print(c)
except ZeroDivisionError as e:
    print(str(e))
    print(repr(e))

print("done")

输出:

division by zero
ZeroDivisionError('division by zero')
done

另外,这个错误类型可以列举,例如:

# Python 3
a = 10
b = 0
try:
    c = b / a
    print(c)
except (IOError, ZeroDivisionError) as x:
    print(x)
else:
    print("no error")

print("done")

输出:

0.0
no error
done

我们发现上面的实例中出现了一个 else 字句,这个的意思估计大家也能猜出来吧 —— 没有异常的时候进行的操作 .

一个完整的 try... 语法结构实际上应该包含 exceptelsefinally 子句,形如:

try:
    ...
except 异常1 as 异常信息1:
    ...
except 异常2 as 异常信息2:
    ...
except:
    ...
else:
    ... # 这里是没有异常时执行的
finally:
    ... # 这里是不管有没有异常最后都执行一下的

我觉得也不需要放实例,非常的明了 .


注意尽量不要在 try ... 语句中使用 raisereturn 这种可能退出程序的语句,例如:

def foo(x):
    try:
        x += 1
        return x 
    finally:
        return x+1

print(foo(11))

输出:

13

这说明,try ... except 语句中使用 return 实际上 finally 子句是会执行的 .

具体的:

  • 在函数中的 try ... except 语句使用 return 后,仍然会执行 finally 中的内容 .
  • finally 使用 return 会导致异常无法回溯 .

还有就是关于性能了,我们尽量是少让它 try ... except,例如下面这个实例:

for i in range(114514):
    try:
        pass
    except:
        pass

就可以改写为

try:
    for i in range(114514):
        pass
except:
    pass

raise 抛出异常

raise 可以抛出一个异常,例如:

print(114514)
raise SyntaxError
print(1919810)

这样中间就会出一个 SyntaxError .

SyntaxError 实际上是一个类,抛出异常时,会自动生成它的一个对象,Python 实际上抛出的是这个对象 .

当然,也可以自行生成对象:

raise SyntaxError()

结语

我觉得异常还是 Python 中比较基础的知识点,希望大家掌握 .


以下是 OI 特供版内容:

在 OI 中的应用

不定行读入

我们知道 Python 读没了会抛出 EOFError .

所以 try ... except 一下就可以判断读没读完,也就可以实现不定行读入了 .

中缀表达式

中缀表达式

输入一个中缀表达式(由 \(0\dots9\) 组成的运算数,加 +-*/(除为整除)四种运算符,左右小括号组成 . 注意 - 也可作为负数的标志,表达式以 @ 作为结束符),判断表达式是否合法,如果不合法,输出 NO;否则请把表达式转换成后缀形式,再求出后缀表达式的值并输出 .

黑盒测试不用管转换后缀表达式,只需要求值即可 .

一开始先把 @ 去掉,这个非常平凡 .

然后 Python 有方法 eval 可以返回一行 Python 语句返回的结果,就像在解释器中直接输入一样 .

这样我们就可以直接使用 eval 来计算了 .

注意这里的除是整除所以要先把 / 全部 replace//,这样才符合 Python 语法 .

eval 如果运行不下去了就会抛出异常,我们根据这个来判断 NO 即可 .

注意抛出的不一定是 SyntaxError,比如下面这组数据:

(5*(5-9)-4)+7-9*2+5(*(+6-2))@

eval 会抛出 TypeError .

下面是完整代码:

import traceback
s = input().split('@')[0].replace("/", "//")
try:
    print(eval(s))
except:
    print("NO")
    traceback.print_exc() # debug
posted @ 2022-08-21 22:05  Jijidawang  阅读(1935)  评论(3编辑  收藏  举报
😅​