Python3 | 练气期,捕获错误异常 、自定义异常处理!

2024-07-29 10:03:20 浏览数 (3)

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ]

0x00 前言简述

在我们开始学习 Python 编程语言的时候, 我们经常会遇到各种错误, 比如:语法错误,运行时错误,逻辑错误等等, 这些错误在开发学习中是不可避免的, 但是随着我们学习的深入可以发现 Python 可以很好的处理这些错误, 并且在正式业务的开发环境中往往都是需要想到不同的报错场景, 然后编写对应的异常处理机制, 下面我们就一起来学习下 Python 中如何进行错误与异常处理,以及如何自定义一个异常类。

weiyigeek.top-Python3 错误和异常图

什么是异常处理机制?

答: 由于环境的不确定性和用户操作的不可以预知性都可能导致程序出现各种问题, 因此异常机制最重要的无非就是,增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动消化并恢复正常(不至于崩溃);


0x01 Python 错误与异常处理

描述: Python 有两种错误很容易辨认,即 语法错误异常 ,在使用 assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常,用于程序在运行时捕捉错误、抛出错误,以及对应的处理方法。

  • 语法错误:或者称为解析错,即编写的程序没有通过语法的检查,这是初学者经常碰到的, 例如:while True print('Hello world'), 由于前面缺少了一个冒号 : 所以在运行程序会报 SyntaxError: invalid syntax 错误。
  • 异常运行期检测到的错误,有时即便编写的程序通过语法的检查,但在执行程序的过程中,也可能发生错误,解释器会尝试处理它,如果处理不了则该程序会终止并提示错误信息,例如:10 * (1/0) 由于 0 不能作为除数,触发异常 ZeroDivisionError 错误,除此之外 Python 还内置一些异常,请在文章末尾或官网查看内置异常及其含义。

1.异常处理

在 Python 中,使用 try...except...finally... 语句来处理异常, 其中 finally 语句主要用于try....except代码块执行后的清理流程,语法格式如下:

代码语言:javascript复制
# 方式1.try - except 语句语法
try:
    检测的代码块
except Exception [as reaon]:
    出现异常后执行的代码

# 方式2.try - except - else 语句语法**
try:
    检测的代码块
except Exception [as reaon]:
    出现异常后执行的代码
else:
    当没有异常发生时,else中的语句将会被执行

# 方式3.try - except - finally 语句
try:
    检测的代码块
except Exception [as reaon]:
    出现异常后执行的代码
else:
    当没有异常发生时,else中的语句将会被执行
finally:
     无论怎么样都会被执行的代码


# 方式4.except 子句 可以用带圆括号的元组来指定多个异常
try:
  检测的代码块
except (RuntimeError, TypeError, NameError):
  pass

weiyigeek.top-try - except - finally语句图

简单示例:

  • 案例1.打开一个文件,如果文件存在则正常读取,如果不存在则抛出异常,最后在退出try语句前关闭文件。
代码语言:javascript复制
try:
    f = open('myfile.txt')
except IOError as err:
    print('IOError', err)
except OSError as err:
    print("OSerror: {0}".format(err))
except ValueError:
    print("无法将数据转换为整数")
except:
    print("未知错误:", sys.exc_info()[0])
else:
    print("无异常时执行.")
finally:
    f.close()
    print("当try块退出时执行.")
  • 案例2.嵌套使用try语句异常捕获案例。
代码语言:javascript复制
# 定义一个函数
def diviede(x,y):
  return x / y

# 异常捕获
try:
    # 先执行 try 子句 中的代码块(包括间接调用)的函数
    diviede(6,3)
except ZeroDivisionError as zerr:
    print("ZeroDivisionError:",zerr)
except AssertionError as aerr:
    print("AssertionError:",aerr)
else:
    # 嵌套异常捕获
    try:
        with open('file.log') as f:
            read_data = f.read()
    except FileNotFoundError as fnferr:
        print("FileNotFoundError",fnferr)
finally:
    print('这句话,无论异常是否发生都会执行。')

# 执行结果:
# FileNotFoundError [Errno 2] No such file or directory: 'file.log'
# 这句话,无论异常是否发生都会执行。

特别注意:

  • 异常处理程序不仅会处理在 try 子句中立刻发生的异常,还会处理在 try 子句 中调用(包括间接调用)的函数。
  • 如果 try 语句时遇到 break,、continue 或 return 语句,则 finally 子句在执行 break、continue 或 return 语句之前执行。
  • 如果 finally 子句中包含 break、continue 或 return 等语句,异常将不会被重新引发。
  • 如果 finally 子句中包含 return 语句,则返回值来自 finally 子句的某个 return 语句的返回值,而不是来自 try 子句的 return 语句的返回值(特别注意)。
代码语言:javascript复制
def bool_return():
  try:
    return True
  finally:
    return False

bool_return()

2.触发异常

在 Python 中,触发异常(也称异常抛出)可以使用 raise 语句,此语句支持强制触发指定的异常,不过其参数必须是异常实例或异常类(派生自 BaseException 类,例如 Exception 或其子类)。

此外还有异常链,若未处理的异常发生在 except 部分内,它将会有被处理的异常附加到它上面,并包括在错误信息中,为了表明一个异常是另一个异常的直接后果, raise 语句允许一个可选的 from 子句,在转换异常时,这种方式很有用,并且它还允许使用 from None 表达禁用自动异常链:。

语法格式:

代码语言:javascript复制
raise [ExceptionType [, args [, traceback]]]

# 常规使用格式
raise 
raise ExceptionType
raise ExceptionType(**args)
raise ExceptionType from [exc, None]

简单示例:

  • 示例1.只想判断是否触发了异常,但并不打算处理该异常,则可以使用更简单的 raise 语句重新触发异常
代码语言:javascript复制
try:
  raise NameError('WeiyiGeek')
except NameError:
  print('An exception flew by!')
  raise

# 执行结果:
  # An exception flew by!
  # ---------------------------------------------------------------------------
  # NameError                                 Traceback (most recent call last)
  # Cell In[10], line 2
  # NameError: WeiyiGeek
  • 示例2. except 子句中的类匹配的异常将是该类本身的实例或其所派生的类的实例(列出派生类的 except 子句 不会匹配其基类的实例)
代码语言:javascript复制
class B(Exception):
    def __init__(slef):
        print("我是基类 B.")
    pass

class C(B):
    def __init__(slef):
        print("我是B类的派生类.")
    pass

class D(C):
    def __init__(slef):
        print("我是C类的派生类.")
    pass

for cls in [B, C, D]:
    print(cls)
    try:
        raise cls()  # 关键点
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")
    finally:
        print('这句话,无论异常是否发生都会执行。')

执行结果:

代码语言:javascript复制
我是基类 B.
B
这句话,无论异常是否发生都会执行。

<class '__main__.C'>
我是B类的派生类.
C
这句话,无论异常是否发生都会执行。

<class '__main__.D'>
我是C类的派生类.
D
这句话,无论异常是否发生都会执行。

温馨提示:将如将把 except B 放在最前,则会输出 B, B, B ,这是因为派生类的基类即触发了第一个匹配的 except 子句。

  • 案例3.异常链的使用演示。
代码语言:javascript复制
# 启动异常链
def func():
  print("open database.sqlite")
  raise ConnectionError

try:
  func()
except ConnectionError as exc:
  raise RuntimeError('Failed to open database') from exc
# 执行结果:
  # open database.sqlite
  # ---------------------------------------------------------------------------
  # ConnectionError                           Traceback (most recent call last)
  # ConnectionError: 
  # The above exception was the direct cause of the following exception:
  # RuntimeError                              Traceback (most recent call last)
  # RuntimeError: Failed to open database
  
# 禁用自动异常链
try:
  open('database.sqlite')
except OSError as exc:
  raise RuntimeError("Failed to open database") from None

# 执行结果:
  # ---------------------------------------------------------------------------
  # RuntimeError                              Traceback (most recent call last)
  # RuntimeError: Failed to open database
  • 案例4.抛出指定元组中类型的异常。
代码语言:javascript复制
try:
  if 'a' > '0':
    raise TypeError("假若为 TypeError.")  # 抛出指定异常
  else:
    raise NameError("假若为 NameError.")  # 抛出指定异常
except (TypeError,NameError) as errvalue:
  print("异常原因:",errvalue) 

# 执行结果:
# 异常原因: 假若为 TypeError.

3.异常组

描述:在 Python 中,异常组是可以多个异常的集合,它允许一个 except 子句处理多个异常,而不是像前面的例子中那样处理每个异常一个 except 子句。

3.1 异常实例的列表

Python 中有一个内置的 ExceptionGroup, 它打包了一个异常实例的列表,由于其本身就是一个异常,所以它可以像其他异常一样被捕获。此外还可通过使用 except* 代替 except ,我们可以有选择地只处理组中符合某种类型的异常。

简单示例:

  • 1.异常组中打包异常的实例
代码语言:javascript复制
def syntax1():
  excs = [OSError('error 1'), SystemError('error 2')] # 含括多个异常实例
  raise ExceptionGroup('there were problems', excs)   # 打包异常实例列表(异常组)

def syntax2():
  raise ExceptionGroup(
    "group1",
    [
      OSError(1),
      SystemError(2),
      ExceptionGroup(
        "group2",[
          OSError(3),
          RecursionError(4)
        ]
      )
    ]
  )

# 1.演示异常组中的异常实例
syntax1()
# 执行结果:
  #   Exception Group Traceback (most recent call last):
  # | ExceptionGroup: there were problems (2 sub-exceptions)
  #  - ---------------- 1 ----------------
  #   | OSError: error 1
  #    ---------------- 2 ----------------
  #   | SystemError: error 2
  #    ------------------------------------
try:
  syntax1()
except Exception as e:
    e.add_note('""')
    print(f'Exception Caught -> {type(e)}: e') 
# 执行结果:
  # Exception Caught -> <class 'ExceptionGroup'>: e

# 2.嵌套的异常组,每个 except* 子句都从组中提取指定类型的异常
try:
  syntax2()
except* OSError as e:
  print("ExceptionGroup -> There were OSErrors.")
except* SystemError as e:
  print("ExceptionGroup -> There were SystemErrors.")
except* RecursionError as e:
  print("RecursionError -> There were RecursionError.")

# 执行结果:
  # ExceptionGroup -> There were OSErrors.
  # ExceptionGroup -> There were SystemErrors.
  # ExceptionGroup -> There were RecursionError.
3.1 异常注释

在一个异常被创建以引发时,它通常被初始化为描述所发生错误的信息,此时我们可以使用 BaseException 异常类型中的 add_note(note) 方法, 以便在异常实例中附加相信额外的信息,并将其添加到异常的注释列表。

代码语言:javascript复制
try:
    raise TypeError('bad type')
except Exception as e:
    e.add_note('Add some information')
    e.add_note('Add some more information')
    raise
# 执行结果:
  # TypeError                                 Traceback (most recent call last)
  # TypeError: bad type
  # Add some information
  # Add some more information

简单示例:

  • 1.当把异常收集到一个异常组时,为各个错误添加上下文信息。
代码语言:javascript复制
def f():
    raise OSError('operation failed')
excs = []
for i in range(3):
    try:
        f()
    except Exception as e:
        e.add_note(f'Happened in Iteration {i 1}')
        excs.append(e)

# 触发异常组
raise ExceptionGroup('We have some problems', excs)

# 执行结果:
  #   Exception Group Traceback (most recent call last):
  # |   File "<stdin>", line 1, in <module>
  # | ExceptionGroup: We have some problems (3 sub-exceptions)
  #  - ---------------- 1 ----------------
  #   | Traceback (most recent call last):
  #   |   File "<stdin>", line 3, in <module>
  #   |   File "<stdin>", line 2, in f
  #   | OSError: operation failed
  #   | Happened in Iteration 1
  #    ---------------- 2 ----------------
  #   | Traceback (most recent call last):
  #   |   File "<stdin>", line 3, in <module>
  #   |   File "<stdin>", line 2, in f
  #   | OSError: operation failed
  #   | Happened in Iteration 2
  #    ---------------- 3 ----------------
  #   | Traceback (most recent call last):
  #   |   File "<stdin>", line 3, in <module>
  #   |   File "<stdin>", line 2, in f
  #   | OSError: operation failed
  #   | Happened in Iteration 3
  #    ------------------------------------

4.自定义异常

描述: 在 Python 3 中许多标准模块定义了自己的异常,以报告他们定义的函数中可能出现的错误,自定义异常是一种用于处理特定错误情况的强大工具,通过定义自定义异常,可以提高代码的可读性和可维护性。

特别注意,自定义异常都应该从 Python 的基类 Exception 继承. 并大多数异常命名都以 “Error” 结尾,类似标准异常的命名,以下是创建和使用自定义异常的一个简单示例:

4.1 简单的自定义异常

1.自定义异常类

首先,我们定义一个自定义异常类。自定义异常类通常继承自内置的 Exception 类。

代码语言:javascript复制
class MyCustomError(Exception):
    """Exception raised for custom errors.

    Attributes:
        message -- explanation of the error
    """

    def __init__(self, message):
        self.message = message
        super().__init__(self.message)

2.使用自定义异常

展示如何在代码中引发和处理该自定义异常。

代码语言:javascript复制
def divide_numbers(a, b):
    if b == 0:
        raise MyCustomError("Division by zero is not allowed")
    return a / b

try:
    result = divide_numbers(10, 0)
except MyCustomError as e:
    print(f"Caught an exception: {e}")

# 执行结果:
# Caught an exception: Division by zero is not allowed

在这个示例中,当尝试除以零时,会引发 MyCustomError 异常,并在 except 块中捕获和处理该异常。

4.2 更复杂的自定义异常

自定义异常可以包含更多信息,例如错误码、上下文等,以下是一个更复杂的自定义异常示例(非常值得大家学习):

代码语言:javascript复制
class DetailedError(Exception):
    """Exception raised for detailed errors.

    Attributes:
        code -- error code
        message -- explanation of the error
    """

    def __init__(self, code, message):
        self.code = code
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f"[Error {self.code}]: {self.message}"

def process_data(data):
    if not isinstance(data, list):
        raise DetailedError(1001, "Data must be a list")
    if not data:
        raise DetailedError(1002, "Data list is empty")
    # Process data...
    return "Processing complete"

try:
    process_data({})
except DetailedError as e:
    print(f"Caught an exception: {e}")

执行结果:

代码语言:javascript复制
Caught an exception: [Error 1001]: Data must be a list

在这个示例中,DetailedError 包含错误码和错误信息,通过覆盖 __str__ 方法,提供了更友好的错误信息格式。

总结自定义异常允许你创建更具描述性和上下文的错误处理机制,通过继承内置的 Exception 类并添加自定义属性和方法,可以更好地管理和调试代码中的错误情况,所以考察一个开发者编程综合能力,就看看是如何处理自己编写代码的错误异常。

5.内置异常

描述: 在 Python 中,所有异常必须为一个派生自 BaseException 的类的实例。 在带有提及一个特定类的 except 子句的 try 语句中,该子句也会处理任何派生自该类的异常类(但不处理 它 所派生出的异常类)。 通过子类化创建的两个不相关异常类永远是不等效的,既使它们具有相同的名称。

作者从Python官网中了解到众多内置异常(bltin-exceptions) 可访问 https://docs.python.org/zh-cn/3/library/exceptions.html#bltin-exceptions 查看详细。

若要捕获除了 SystemExitKeyboardInterruptGeneratorExit 之外的所有异常,可以使用 Exception , 如果你还想捕获这三个异常,将 Exception 改成 BaseException 即可。

代码语言:javascript复制
try:
   ...
except Exception as e:
   ...
   log('Caught:', e)       # Important!

以树形展示 Python 内置异常(bltin-exceptions) 层次结构:

代码语言:javascript复制
BaseException    # 所有内置异常的基类
 ├── BaseExceptionGroup # 异常组,可将下述异常打包到序列 excs 中
 ├── GeneratorExit      # 当 generator 或 coroutine 被关闭时将被引发
 ├── KeyboardInterrupt  # 当用户按下中断键 (通常为 Control-C 或 Delete) 时将被引发
 ├── SystemExit         # 当执行 sys.exit() 函数引发
 └── Exception          # 所有内置的非系统退出类异常都派生自此类
      ├── ArithmeticError   # 当各种算术类错误而引发的内置异常
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError   # 当 assert 语句失败时将被引发。
      ├── AttributeError   # 当属性引用 (参见 属性引用) 或赋值失败时将被引发。
      ├── BufferError      # 当与 缓冲区 相关的操作无法执行时将被引发
      ├── EOFError         # 当 input() 函数未读取任何数据即达到文件结束条件 (EOF) 时将被引发。 
      ├── ExceptionGroup [BaseExceptionGroup]
      ├── ImportError      # 当 import 语句尝试加载模块遇到麻烦时将被引发
      │    └── ModuleNotFoundError
      ├── LookupError     # 当映射或序列所使用的键或索引无效时引发的异常
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError     # 当一个操作耗尽内存但情况仍可(通过删除一些对象)进行挽救时将被引发。
      ├── NameError       # 当某个局部或全局名称未找到时将被引发。
      │    └── UnboundLocalError
      ├── OSError         # 系统函数返回系统相关的错误时将被引发,此类错误包括 I/O 操作失败例如 "文件未找到" 或 "磁盘已满" 等(不包括非法参数类型或其他偶然性错误)。
      │    ├── BlockingIOError
      │    ├── ChildProcessError
      │    ├── ConnectionError
      │    │    ├── BrokenPipeError
      │    │    ├── ConnectionAbortedError
      │    │    ├── ConnectionRefusedError
      │    │    └── ConnectionResetError
      │    ├── FileExistsError
      │    ├── FileNotFoundError
      │    ├── InterruptedError
      │    ├── IsADirectoryError
      │    ├── NotADirectoryError
      │    ├── PermissionError
      │    ├── ProcessLookupError
      │    └── TimeoutError
      ├── ReferenceError   # 当使用 weakref.proxy() 函数所创建的弱引用来访问该引用的某个已被作为垃圾回收的属性时被引发。 
      ├── RuntimeError     # 当检测到一个不归属于任何其他类别的错误时将被引发
      │    ├── NotImplementedError
      │    └── RecursionError
      ├── StopAsyncIteration
      ├── StopIteration
      ├── SyntaxError     # 当解析器遇到语法错误时引发。
      │    └── IndentationError
      │         └── TabError
      ├── SystemError     # 当解释器发现内部错误,但情况看起来尚未严重到要放弃所有希望时将被引发。
      ├── TypeError       # 当一个操作或函数被应用于类型不适当的对象时将被引发。
      ├── ValueError      # 当操作或函数接收到具有正确类型但值不适合的参数,并且情况不能用更精确的异常例如 IndexError 来描述时将被引发。
      │    └── UnicodeError
      │         ├── UnicodeDecodeError
      │         ├── UnicodeEncodeError
      │         └── UnicodeTranslateError
      └── Warning         # 所有警告类别的基类。
           ├── BytesWarning
           ├── DeprecationWarning
           ├── EncodingWarning
           ├── FutureWarning
           ├── ImportWarning
           ├── PendingDeprecationWarning
           ├── ResourceWarning
           ├── RuntimeWarning
           ├── SyntaxWarning
           ├── UnicodeWarning
           └── UserWarning

0 人点赞