Python 基础学习10:异常

程序运行时通常有两种情况:正常和异常。异常事件可能是错误也可能是其它没有预料的事件,Python 提供了异常处理机制,在面临异常时可以帮助处理,而不是通过条件语句一一的去处理,况且总有预料不到的错误。

异常

Python 使用异常对象来表示异常状态,当遇到错误时将引发异常。
当异常对象没有被处理(也称为捕获)时,程序会终止并显示错误的信息(traceback)

1 / 0
Traceback (most recent call last):
  File "<pyshell#59>", line 1, in <module>
    1 / 0
ZeroDivisionError: division by zero

异常对象都属于某个类,可以通过多种方式引发和捕获异常实例,从而采取措施,而不是让程序失败终止。

raise 语句

当出现问题是,会自动引发异常,也可以主动的引发异常。
使用 raise 语句,并将一个类(必须是 Exception 的子类)或实例作为参数。
将类作为参数时会自动创建一个实例。

raise Exception
Traceback (most recent call last):
  File "<pyshell#62>", line 1, in <module>
    raise Exception
Exception

可以在引发错误时,指出发生的信息:

raise Exception('hyperdrive overload')
Traceback (most recent call last):
  File "<pyshell#64>", line 1, in <module>
    raise Exception('hyperdrive overload')
Exception: hyperdrive overload

有很多内置的异常类,都可以用于 raise 语句中:

类名描述
Exception几乎所有的异常类都是基于它派生的
AttributeError引用属性或给它赋值失败时引发的
OSErrorOS 不能执行指定的任务时引发,有多个子类
IndexError使用序列中不存在索引时引发,为 LookupError 的子类
KeyError使用隐射中不存在的键时引发,为 LookupError 的子类
NameError找不到名称时引发
SyntaxError代码不正确时引发
TypeError将内置操作或函数用于类型不正确的对象时引发
ValueError将内置操作或函数用于类型正确但包含的值不合适时引发
ZeroDivisionError在除法或求模运算的第二参数为零时引发

自定义异常类

虽然内置异常类涉及的范围很广,但可能需要实现自己的需求,也可以创建自定义异常类。
创建自定义异常类,与创建普通类是一样的,但是务必直接或间接的继承 Exception 类。

class CustomError(Exception): pass

捕获异常

异常是可以对其进行处理的,称为捕获异常。
使用 try/except 语句来捕获异常:

try:
    1 / 0
except:
    print("the second number can't be zero!")


the second number can't be zero!

重新引发

捕获异常后,如果要重新引发它(继续向上传播)可使用 raise 且不提供任何参数。
在设计程序时可以设置一个“抑制”开头,用来控制异常是否要继续向上传播。

class MuffledCalculator:
    muffled = False
    def calc(self, expr):
        try:
            return eval(expr)
        except ZeroDivisionError:
            if self.muffled:
                print('Division by zero is illegal')
            else:
                raise


calculator = MuffledCalculator()
calculator.calc('10/2')
5.0

calculator.calc('10/0')
Traceback (most recent call last):
  File "<pyshell#90>", line 1, in <module>
    calculator.calc('10/0')
  File "<pyshell#87>", line 5, in calc
    return eval(expr)
  File "<string>", line 1, in <module>
ZeroDivisionError: division by zero

calculator.muffled = True
calculator.calc('10/0')
Division by zero is illegal

有时可能想引发其它的异常,进入 except 子句的异常将作为异常上下文存储起来,并出现最终的错误消息中:

try:
    1 / 0
except ZeroDivisionError:
    raise ValueError

Traceback (most recent call last):
  File "<pyshell#100>", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<pyshell#100>", line 4, in <module>
    raise ValueError
ValueError

可以使用 raise ... from ... 来提供自己的上下文,使用 None 可以禁用上下文:

try:
    1 / 0
except ZeroDivisionError:
    raise ValueError from None

Traceback (most recent call last):
  File "<pyshell#102>", line 4, in <module>
    raise ValueError from None
ValueError

多个 except 子句

在进行除法运算时,除数是零会引发异常,除数时非数值型也会产生异常,所以仅仅捕获 ZeroDivisionError 是不够的。可以在 try/except 语句中添加一个 except 子句:

try:
    x = int(input('Enter the first number: '))
    y = int(input('Enter the second number: '))
    value = x / y
    print('x / y is ', value)
except ZeroDivisionError:
    print("The second number can't be zero!")
except TypeError:
    print("That wasn't a number, was it?")

如果要使用一个 except 子句捕获多种异常,可以使用元组来指定异常类型:

try:
    x = int(input('Enter the first number: '))
    y = int(input('Enter the second number: '))
    value = x / y
    print('x / y is ', value)
except (ZeroDivisionError, TypeError):
    print('Input a wrong number!')

捕获对象

可以在 except 子句中访问异常对象(实例)本身,如果是捕获多个异常(元组指定多个异常类型)时,也是返回一个对象。

try:
    x = int(input('Enter the first number: '))
    y = int(input('Enter the second number: '))
    value = x / y
    print('x / y is ', value)
except (ZeroDivisionError, TypeError) as e:
    print(e)

捕获所有异常

即使处理了好几种异常,可能还有一些没有预料到的异常,通常没有必要去隐藏没有被发现的异常,而是让程序立即崩溃,要捕获所有异常,只需在 except 语句中不指定任何异常类即可。

异常捕获中使用 else

可以像条件语句和循环一样,给捕获异常添加一个 else 子句:

try:
    print('hello')
except:
    print('what?')
else:
    print('Ah..')


hello
Ah..

当出现异常时:

try:
    1 / 0
    print('hello')
except:
    print('what?')
else:
    print('Ah..')


what?

仅当没有引发异常时,才会执行 else 中的语句。

else 也可以实现跳出循环,配合异常捕获:

while True:
    try:
        x = int(input('Enter the first number: '))
        y = int(input('Enter the second number: '))
        value = x / y
        print('x / y is ', value)
    except Exception as e:
        print('Invalid input,', e)
        print('pls try again.')
    else:
        break

当没有引发异常时将执行 else 语句中的 break 进而跳出循环。

finally 子句

finally 子句可用于在发生异常时执行清理工作,是 try 子句配套的命令。

x = None
try:
    1 / 0
finally:
    print('Cleaning up ...')
    del x

使用 del 删除变量可能是不太适宜,但是 finally 非常适合用于确保文件或网络套接字等资源的关闭。
在一条语句中同时包含 try except finally else

try:
    1 / 0
except NameError:
    print('error value')
else:
    print('well done')
finally:
    print('Cleaning up')

异常与函数

如果不处理函数中引发的异常,它将向上传播到调用函数的地方,如果调用的位置没有处理,异常将继续传播,直至到达主程序(全局作用域)。如果主程序中也没有处理异常,程序将终止并显示跟踪堆栈。

def faulty():
    raise Exception('Something is wrong')

def ignore_exception():
    faulty()


def handle_exception():
    try:
        faulty()
    except:
        print('Exception handled')



ignore_exception()
Traceback (most recent call last):
  File "/usr/lib64/python3.12/idlelib/run.py", line 580, in runcode
    exec(code, self.locals)
  File "<pyshell#28>", line 1, in <module>
  File "<pyshell#20>", line 2, in ignore_exception
  File "<pyshell#14>", line 2, in faulty
Exception: Something is wrong

handle_exception()
Exception handled

最佳实践

可以使用条件语句来表达异常处理实现的目标,但这边编写出来的代码可能不那么自然,可读性没有那么高。
很多情况下,相比与使用 if/else ,使用 try/except 语句更加自然,也更符合 Python 的风格,尽量养成尽可能使用 try/except 语句的习惯。

不那么异常的情况

如果只想发出警号,可使用模块 warnings 中的 warn 函数。

from warnings import warn
warn('warning')

Warning (from warnings module):
  File "<pyshell#34>", line 1
UserWarning: warning

warn('warning')
warn('important')

Warning (from warnings module):
  File "<pyshell#36>", line 1
UserWarning: important

警告只会显示一次,如果再次执行最后一行,什么事情都不会发生。

可使用模块 warnings 中的函数 filterwarning 来抑制发出的警告(或特定类型的警告),并指定要采取的措施(error 或 ignore)。

from warnings import filterwarnings
filterwarnings("ignore")
warn('warning')
filterwarnings("error")
warn('warning')
Traceback (most recent call last):
  File "<pyshell#44>", line 1, in <module>
    warn('warning')
UserWarning: warning

还可以根据异常来过滤掉特定类型的警告:

filterwarnings("ignore",category=DeprecationWarning)
warn('This func is too old.', DeprecationWarning)

filterwarnings("error",category=DeprecationWarning)
warn('This func is too old.', DeprecationWarning)
Traceback (most recent call last):
  File "<pyshell#54>", line 1, in <module>
    warn('This func is too old.', DeprecationWarning)
DeprecationWarning: This func is too old.

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

滚动至顶部