前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >09. 异常处理

09. 异常处理

作者头像
有一只柴犬
发布2024-02-03 08:26:24
1020
发布2024-02-03 08:26:24
举报
文章被收录于专栏:JAVA体系JAVA体系

1、前言

在编程中,异常(Exception)是程序在运行期间检测到的错误或异常状况。当程序执行过程中发生了一些无法继续执行的错误时,会引发异常,这可能是由于错误的输入、文件不存在、网络连接问题等多种原因引起的。而程序中对于异常的处理,是为了保持良好的程序健壮性,不会因为异常而导致程序终止甚至退出。

2、常见的异常

在Python中,异常是一个类的实例,通常是内置的异常类的子类。当某个异常条件触发时,Python会抛出(raise)一个异常对象,然后程序的控制流将被转移到处理该异常的代码块。异常处理的机制允许程序员在程序中检测并处理错误,以避免程序崩溃。

一般来说,异常包含了3大部分:异常类型、异常信息、异常堆栈。

1)异常类型:异常类型是指异常的分类,它指定了异常的种类。指示了引发异常的具体情况。

代码语言:javascript
复制
try:
    result = 10 / 0  # 引发 ZeroDivisionError
except ZeroDivisionError as e:
    # 异常类型是 ZeroDivisionError
    print(f"Error Type: {type(e).__name__}")

2)异常信息:是一条包含有关异常原因的人类可读的描述。异常消息通常包含错误的详细信息,有助于开发者理解异常的具体原因。

代码语言:javascript
复制
try:
    result = int("abc")  # 引发 ValueError
except ValueError as e:
    # 异常消息是 "invalid literal for int() with base 10: 'abc'"
    print(f"Error Message: {str(e)}")

3)异常堆栈:异常堆栈信息包含了异常发生时程序调用栈的状态,它记录了异常的触发点以及导致异常的函数调用链。异常堆栈信息对于调试和定位问题非常有用。

代码语言:javascript
复制
def func1():
    result = int("abc")  # 引发 ValueError

def func2():
    func1()

try:
    func2()
except ValueError as e:
    # 异常堆栈信息包含了函数调用链
    print(f"Exception Traceback: {e.with_traceback(e.__traceback__)}")

而在Python中常见的异常类有:

  • ZeroDivisionError:除以零错误
  • ValueError:传入一个调用者不期望的值,即使值的类型是正确的
  • TypeError:操作或函数的参数类型错误
  • FileNotFoundError:文件不存在错误
  • IndexError:索引超出序列范围
  • KeyError:字典中的键不存在
  • NameError:尝试访问未声明的变量

3、异常处理try...except...finally

传统来讲,如果程序在运行过程中发生了异常,可以实现约定好一些错误码,利用错误码来区分各种异常事件,典型的诸如Http状态码。这样根据不同的错误码就可以很清楚的知道是什么错误类型。但是如果错误码很多,那么维护起来就很不方便,而且错误码通常需要和业务代码结合在一起:

代码语言:javascript
复制
def method1():
    code = do_something()
    if code == 200:
        return "ok"
    elif code == 404:
        return "resource not found"
    elif code == 500:
        return "server internal error"
    ......
    else:
        return "other error"


def do_something():
    return 100

因此,Python内置了一套异常处理机制。在 Python 3 中,异常处理是通过使用 try, except, else, 和 finally 等关键字来实现的。异常处理的目的是在程序执行期间检测到错误,并提供一种机制来处理这些错误,防止程序中断或崩溃。以下是异常处理的基本语法:

代码语言:javascript
复制
try:
    # 可能引发异常的代码块
    result = 10 / 0  # 这里故意引发一个除零错误
except ZeroDivisionError as e:
    # 异常处理块
    print(f"Error: {e}")
else:
    # 如果没有发生异常时执行的代码块
    print("No exception occurred.")
finally:
    # 无论是否发生异常,都会执行的代码块
    print("Finally block executed.")

try 语句包裹了可能引发异常的代码块。如果在 try 语句中发生异常,程序将跳转到匹配的 except 语句块,执行相应的异常处理逻辑。如果没有异常发生,那么会执行 else 语句块中的代码。最后,无论是否发生异常,都会执行 finally 语句块中的代码。

上述代码执行后的结果:

当然这里的except捕获的异常可以有不同类型,如:

代码语言:javascript
复制
def test():
    try:
        # 可能引发异常的代码块
        result = 10 / 0  # 这里故意引发一个除零错误,会抛出ZeroDivisionError
        result = 10 / int('a')  # 这里故意引发一个字符串转换类型错误,会抛出ValueError
        "hello" + b   # 这里故意引用一个未被声明的变量,会抛出NameError
    except ValueError as ve:  # 当发生ValueError时候,被这里的异常捕获
        # 异常处理块
        print(f"raise a exception : ValueError: {ve}")
    except ZeroDivisionError as ze:  # 当发生ZeroDivisionError时候,被这里的异常捕获
        # 异常处理块
        print(f"raise a exception : ZeroDivisionError: {ze}")
    except Exception as e:     # 当发生的异常上面都没有捕获时,最终会被这层捕获
        # 异常处理块
        print(f"raise a exception : Exception: {e}")
    else:
        # 如果没有发生异常时执行的代码块
        print("No exception occurred.")
    finally:
        # 无论是否发生异常,都会执行的代码块
        print("Finally block executed.")


test()

需要注意的是,这里的异常是逐层捕获的,越靠经try的except优先级越高。如果第一层except就捕获了Exception,那么接下来的ValueError都是捕获不到的。

代码语言:javascript
复制
def test():
    try:
        # 可能引发异常的代码块
        result = 10 / 0  # 这里故意引发一个除零错误,会抛出ZeroDivisionError
        # result = 10 / int('a')  # 这里故意引发一个字符串转换类型错误,会抛出ValueError
        "hello" + b   # 这里故意引用一个未被声明的变量,会抛出NameError
    except Exception as ve:  # 调换一下顺序,把Exception放在第一层
        # 异常处理块
        print(f"raise a exception : Exception: {ve}")
    except ZeroDivisionError as ze:  # 当发生ZeroDivisionError时候,被这里的异常捕获
        # 异常处理块
        print(f"raise a exception : ZeroDivisionError: {ze}")
    except ValueError as e:     # 当发生的异常上面都没有捕获时,最终会被这层捕获
        # 异常处理块
        print(f"raise a exception : ValueError: {e}")
    else:
        # 如果没有发生异常时执行的代码块
        print("No exception occurred.")
    finally:
        # 无论是否发生异常,都会执行的代码块
        print("Finally block executed.")


test()

照理说,10/0会抛出ZeroDivisionError异常类型,但是由于Exception类型比ZeroDivisionError更靠近try,所以优先被Exception捕获。

因为Exception是所有异常类的基类。ValueError或NameError等异常都继承于Exception,因此Exception可以捕获所有属于它自己的子类异常类型。如果不存在继承关系,那么优先级属于平级,就会按照异常类型各自捕获。因此项目中,我们往往会把Exception最为保底的异常捕获类型来处理。

我们点开ValueError源码可以看到继承关系:

此外,使用try...except还有一个好处是,它可以跨层调用。如:

代码语言:javascript
复制
def test():
    try:
        result = test1()  # 这里调用test1方法,test1方法会抛出异常,由上层捕获
    except Exception as ve: 
        # 异常处理块
        print(f"raise a exception : Exception: {ve}")
    finally:
        # 无论是否发生异常,都会执行的代码块
        print("Finally block executed.")


def test1():
    return 1 / 0


test()

这样,我们就不需要在每个调用方法的地方都进行异常捕获,只要在合适的层(如在统一入口进行捕获)就可以捕获到各个层次间的异常信息。而如果异常没有被捕获,则会一直网上抛,直到被Python解释器捕获,然后程序退出。

4、异常信息解读

上面我们介绍了基本的异常处理的语法。既然出现了异常,那么我们肯定是要进行修复的。那么读懂异常信息就很关键。前面介绍到异常一般分为3个部分,异常类型和异常信息就不说了,通常都很容易看懂。主要我们来看下异常堆栈,这里包含了异常的整个方法调用链,从中我们可以很容易看到具体哪个方法出现了异常。我们先来编写一段代码,模拟下异常:

代码语言:javascript
复制
def do_something():
    return 1 / 0


def test():
    try:
        result = do_something()  # 这里调用do_something方法,do_something方法会抛出异常,由上层捕获
    except Exception as e:
        # 异常处理块,使用with_traceback()打印出异常堆栈信息
        print(f"raise a exception : Exception: {e.with_traceback()}")
    finally:
        # 无论是否发生异常,都会执行的代码块
        print("Finally block executed.")


test()

执行结果:

所以可以看到,通过跟踪异常的堆栈信息,可以很容易定位到具体的错误代码。

注:使用e.with_traceback()打印的错误信息,只能在控制终端打印信息,并不能持久化。一般项目中需要把错误信息记录的日志文件中,方便排查。可以引入logging模块,使用logging记录到日志中

5、raise

除了try...except被动的捕获程序异常以外,我们还可以手动的进行抛出已识别的异常信息。这时就要用到raise关键字。通过 raise 关键字,你可以显式地引发异常,并指定异常类型、异常消息等信息。这对于在特定条件下主动引发异常、或在异常发生时进行额外的信息记录非常有用。

基础语法很简单:

代码语言:javascript
复制
raise 异常类("异常信息")

如:

代码语言:javascript
复制
def example_function(value):
    if value < 0:
        raise ValueError("这里引发一个异常,value值不能<0")
    return value


try:
    result = example_function(-5)
except ValueError as e:
    print(f"捕获到异常: {e}")

example_function 函数中使用 raise 关键字在 value 小于 0 时引发了 ValueError 异常,并提供了异常消息。在异常处理块中,程序捕获了这个异常并进行了处理。raise 语句可以包含一个异常类、一个异常类的实例,或者是一个异常类和一个异常消息:

代码语言:javascript
复制
# 引发指定类型的异常
raise ValueError("This is a custom error message")

# 引发异常实例
custom_exception = ValueError("This is another custom error message")
raise custom_exception

使用 raise 关键字时需要注意,在没有捕获异常的情况下,异常会传递到调用栈的上层,直到被捕获或导致程序终止。因此,要慎重使用 raise,确保异常能够得到适当处理。

6、自定义异常

通常,结合raise使用的需要我们自定义异常类。根据不同的业务场景,定义符合业务场景类型的异常类。编写自定义异常时,需要继承异常的基类(Exception)或其子类,并在构造函数中设置一些自定义属性。如:

代码语言:javascript
复制
def example_function(value):
    if value < 0:
        raise CustomError(-500, "这里引发一个异常,value值不能<0")
    return value


# 自定义异常CustomError,继承Exception
class CustomError(Exception):
    # 构造函数,需要提供异常代码,异常信息属性
    def __init__(self, code, message):
        self.code = code
        self.message = message
        super().__init__(message)


try:
    result = example_function(-5)
except ValueError as e:
    print(f"捕获到异常: {e}")

CustomError 类继承自 Exception,并在其构造函数中定义了两个属性 code 和 message。在 example_function 中,当输入值小于 0 时,引发了自定义的异常,并在异常处理块中捕获并输出了异常的属性信息。

自定义异常的主要目的是提供更多的上下文信息,以便在异常发生时更好地理解问题的原因。在实际的应用中,可以根据具体的需求定义不同的自定义异常类,以便更好地组织和处理异常情况。

7、小结

总体来说,异常处理是一种良好的编程实践,有助于确保程序在面对各种异常情况时能够保持可控和可维护。通过适当的异常处理,开发者能够更好地应对意外情况,提高程序的质量和稳定性。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、前言
  • 2、常见的异常
  • 3、异常处理try...except...finally
  • 4、异常信息解读
  • 5、raise
  • 6、自定义异常
  • 7、小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com