Python的异常机制

Python
49
0
0
2024-03-24

一、异常与错误

Python机制设置了异常机制。异常指的是运行时程序遇到的可以被捕捉的错误。程序捕捉了异常,而不至于让程序运行错误而crash。异常增强了程序的运行可靠性。

我们来看个例子来对比下错误和异常的区别

  • 错误:
import sys
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())

print("code reachs here.")
  • 异常:
import sys
  
try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error:", err)
except ValueError:
    print("Could not convert data to an integer.")
except Exception as err:
    print(f"Unexpected {err=}, {type(err)=}")
    raise
    
print("code reachs here.")

我们可以看到运行了带异常捕获的例子,程序打印出了code reachs here

二、异常的分类

2.1 自定义异常

异常的基类Exception,一般我们继承Excpetion类来自定义异常类。

自定义异常类往往只提供一些属性保持简单,运行程序提取有关错误的信息。

大多数异常命名都以 “Error” 结尾,类似Python标准提供异常的命名。

比如说这种写法:

class AppException(Exception):
    """ Application exception, will not be logged ass an error """
    pass
    
class UnknownSSIDError(AppException):
    def __init__(self, ssid):
        self.ssid = ssid
        super(UnknownSSIDError, self).__init__("Unknown ssid '%s'" % ssid)

2.2 内置异常分类

BaseException是所有异常的共同基类。它的一个子类Exception是所有非致命异常的基类。不是Exception的子类的异常通常不被处理。它们被用来指示程序应该终止,包括由sys.exit()引发的SystemExit,以及当用户希望中断程序时引发的 KeyboardInterrupt。

完整的Python的Excpetion见下图:

BaseException
 ├── BaseExceptionGroup
 ├── GeneratorExit
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ExceptionGroup [BaseExceptionGroup]
      ├── ImportError
      │    └── ModuleNotFoundError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      │    └── UnboundLocalError
      ├── OSError
      │    ├── BlockingIOError
      │    ├── ChildProcessError
      │    ├── ConnectionError
      │    │    ├── BrokenPipeError
      │    │    ├── ConnectionAbortedError
      │    │    ├── ConnectionRefusedError
      │    │    └── ConnectionResetError
      │    ├── FileExistsError
      │    ├── FileNotFoundError
      │    ├── InterruptedError
      │    ├── IsADirectoryError
      │    ├── NotADirectoryError
      │    ├── PermissionError
      │    ├── ProcessLookupError
      │    └── TimeoutError
      ├── ReferenceError
      ├── RuntimeError
      │    ├── NotImplementedError
      │    └── RecursionError
      ├── StopAsyncIteration
      ├── StopIteration
      ├── SyntaxError
      │    └── IndentationError
      │         └── TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
      │    └── UnicodeError
      │         ├── UnicodeDecodeError
      │         ├── UnicodeEncodeError
      │         └── UnicodeTranslateError
      └── Warning
           ├── BytesWarning
           ├── DeprecationWarning
           ├── EncodingWarning
           ├── FutureWarning
           ├── ImportWarning
           ├── PendingDeprecationWarning
           ├── ResourceWarning
           ├── RuntimeWarning
           ├── SyntaxWarning
           ├── UnicodeWarning
           └── UserWarning

三、异常处理

这段代码我们看到了几个关键动作。

  • 一个是raise 向外抛出了异常
  • 外面用try...except...捕获了异常。try语句可以有多个except 子句来为不同的异常指定处理程序。 但最多只有一个处理程序会被执行。 except 子句可以用带圆括号的元组来指定多个异常。此外try除了except还可以带else。
  • 捕获异常的优先级分别是各个的except从上到下去比对。且认为派生类异常会等于基类异常。所以在第一个for循环我们看到了输出,D-C-B。但是下一个for循环我们会看到输出,B-B-B。是因为第一个excpet B截获了所有的Exception。
class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

for cls in [B, C, D]:
    try:
        raise cls()
    except B:
        print("B")
    except C:
        print("C")
    except D:
        print("D")

3.1 try...except...else

try ... except ... else ...finally...

  • finally子句是可选的,它是try语句结束前执行的最后一项任务。不论try语句是否触发异常,都会执行到finally子句。
  • else也是可选的。如果存在else,那么必须将else放在所有 except 子句 之后。 它适用于 try 子句 没有引发异常但又必须要执行的代码。 例如:
for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

异常处理程序不仅会处理在 try 子句 中立刻发生的异常,还会处理在 try 子句 中调用(包括间接调用)的函数。

处理 Exception 最常见的模式是打印或记录异常,然后重新raise(允许调用者也处理异常):

3.2 添加异常追踪栈

异常是可以添加个性化信息的,比如说下面这个例子。我们可以通过args或者直接print这个异常输出个性化信息。

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(type(inst))    # the exception type
    print(inst.args)     # arguments stored in .args
    print(inst)          # __str__ allows args to be printed directly,
                         # but may be overridden in exception subclasses
    x, y = inst.args     # unpack args
    print('x =', x)
    print('y =', y)

# output:
#<class 'Exception'>
#('spam', 'eggs')
#('spam', 'eggs')
#x = spam
#y = eggs

除了在Exception被创建的时候添加信息,我们还可以在Exception被处理的时候通过add_note方法追加信息。这时候我们添加这层堆栈的信息再raise出去给调用者捕获,那么这个Exception通过层层add_note就有个堆栈的全貌。

add_note(note) 方法接受一个字符串,并将其添加到异常的注释列表。标准的回溯在异常之后按照它们被添加的顺序呈现所有的注释。

try:
    raise TypeError('bad type')
except Exception as e:
    e.add_note('Add some information')
    e.add_note('Add some more information')
    raise

#Traceback (most recent call last):
#  File "<stdin>", line 2, in <module>
#TypeError: bad type
#Add some information
#Add some more information