错误与异常

14、错误和异常

在我们刚开始接触python时,我们经常会遇到程序的报错,这是无论在哪个阶段都是无法避免的,毕竟是人就会犯错的。

接下来我们先从错误开始。

1、错误

对于错误的普遍认知的是语法错误,是初学者常常出现的错误,例如:

>>> 而 True print('Hello world')
File “<stdin>”,第 1 行,在 ?
而 True print('Hello world')
^
SyntaxError:语法无效

这个例子中,函数 print() 被检查到有错误,是它前面缺少了一个冒号  

语法分析器指出了出错的一行,并且在最先找到的错误的位置标记了一个小小的箭头。

2、异常

即便 Python 程序的语法是正确的,在运行它的时候,也有可能发生错误。运行期检测到的错误被称为异常。

这个运行期检测到的错误可以这么理解——拿游戏举例,当你进入游戏时突然网络中断,是不是会跳出提示你网络中断的界面,这个就可以理解为异常处理的一种。

异常处理的基本流程:

  1. 异常的抛出(Raising):在程序执行过程中,当遇到不符合预期的情况或错误时,通过 raise 语句抛出一个异常对象。这个异常对象可以是 Python 内置的异常类型,也可以是自定义的异常类型。

  2. 异常的捕获(Catching):使用 try-except 语句块来捕获可能抛出的异常。try 子句中包含可能会引发异常的代码。

  3. 异常的处理(Handling):在 except 子句中指定要捕获的异常类型,并编写相应的处理代码。可以针对不同类型的异常进行不同的处理。

大多数的异常都不会被程序处理,都以错误信息的形式展现在这里:

>>> 10 * (1/0) # 0 不能作为除数,触发异常
Traceback (the latest call last):
File “<stdin>”, line 1, in ?
ZeroDivisionError: 除以零
>>> 4 + spam*3 # spam 未定义,触发异常
Traceback (the latest call last):
File “<stdin>”, line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2 # int 不能与 str 相加,触发异常
Traceback (the recent call last ):
File “<stdin>”, line 1, in <module>
TypeError:只能将 str(而不是“int”)连接到 str

异常以不同的类型出现,这些类型都作为信息的一部分打印出来: 例子中的类型有 ZeroDivisionError,NameError 和 TypeError。

错误信息的前面部分显示了异常发生的上下文,并以调用栈的形式显示具体信息。

还是之前游戏那个例子,无论因为什么问题而导致的异常,总是需要备用处理方案,来提示是因为什么而发生的异常或者其他运行结果。

对于这个异常的处理有以下几种:

1、try/except 语句。

while True:
    try:
        x = int(input("请输入一个数字: "))
        break
    except ValueError:
        print("您输入的不是数字,请再次尝试输入!")

上述例子中,让用户输入一个合法的整数,用户中断这个程序。用户中断的信息会引发一个 KeyboardInterrupt 异常。

try 语句按照如下方式工作;

  • 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)。

  • 如果没有异常发生,忽略 except 子句,try 子句执行后结束。

  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。

  • 如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。

一个 try 语句可能包含多个 except子句,分别来处理不同的特定异常。最多只有一个分支会被执行。

处理程序将只针对对应的 try 子句中的异常进行处理,而不是其他的 try 的处理程序中的异常。

一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:

except (RuntimeError, TypeError, NameError):
    pass

2、try/except 语句还有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。

else 子句将在 try 子句没有发生任何异常的时候执行。

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

以下实例在 try 语句中判断文件是否可以打开,如果打开文件时正常的没有发生异常则执行 else 部分的语句,读取文件内容

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

使用 else 子句比把所有的语句都放在 try 子句里面要好,这样可以避免一些意想不到,而 except 又无法捕获的异常。

异常处理并不仅仅处理那些直接发生在 try 子句中的异常,而且还能处理子句中调用的函数(甚至间接调用的函数)里抛出的异常。

>>> def this_fails():
        x = 1/0
   
>>> try:
        this_fails()
    except ZeroDivisionError as err:
        print('Handling run-time error:', err)
   
Handling run-time error: int division or modulo by zero

3、try-finally 语句

try-finally 语句无论是否发生异常都将执行最后的代码。

举例:

try:
    runoob()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('这句话,无论异常是否发生都会执行。')
4、抛出异常

抛出异常可以理解为一种在程序运行过程中,当出现不符合预期或错误的情况时,主动发出的“警报”或“信号”。

想象一个场景,比如您在一家餐厅点餐。您告诉服务员您想要一份披萨,但餐厅没有披萨的原料了。这时,服务员就相当于程序中的某个部分,他不能默默地忽略您的需求,而是需要告诉您(或者餐厅经理)无法提供披萨,这就是抛出了一个“没有披萨原料”的异常。

在编程中,当某些条件不满足、出现错误或者遇到不可处理的情况时,程序会抛出一个异常来表明出现了问题。这个异常会中断当前的正常执行流程,让程序能够采取相应的措施来处理这个问题,比如记录错误信息、尝试其他解决方法或者直接终止程序。

异常通常具有特定的类型和相关的错误信息,以更准确地描述问题的性质。

语法格式

raise [Exception [, args [, traceback]]]

#如果 x 大于 5 就触发异常:
x = 10
if x > 5:
    raise Exception('x 不能大于 5。x 的值为: {}'.format(x))
def divide(a, b):
    if b == 0:
        raise ValueError("除数不能为零")  # 抛出异常
    return a / b
​
try:
    result = divide(5, 0)  # 这里会抛出异常
except ValueError as e:  # 异常处理
    print(e)

在上述示例中,divide 函数中当除数为 0 时抛出异常,而 try-except 部分则是对这个异常的处理。

但是要注意的是,抛出异常和正常的异常处理是有区别的。

抛出异常是指在程序运行过程中,当特定的条件不满足或出现错误情况时,主动创建并引发一个异常对象,以表明出现了异常状况。

而正常的异常处理则是指当异常被抛出后,程序中用于捕获、处理和响应这个异常的部分。它包括使用 try-except 语句块来捕获特定类型的异常,并在捕获到异常后执行相应的处理逻辑,例如记录错误信息、采取纠正措施、返回默认值或者以其他方式优雅地处理异常情况,以避免程序崩溃。

简单来说,抛出异常是产生问题的信号,而异常处理是对这个信号的接收和应对。

5、用户自定义异常

用户自定义异常是指在 Python 中,您自己创建的特定类型的异常类,以更准确地表示您的程序中可能出现的独特错误情况。

这就好比在一个大的“异常家族”中,Python 已经提供了一些常见的“成员”(如 ValueErrorTypeError 等)来表示一些通用的错误。但有时候,您的程序可能会遇到一些特殊的、与众不同的错误情况,这些通用的异常并不能很好地描述。

例如,如果您正在编写一个与网络通信相关的程序,可能会定义一个名为 NetworkTimeoutException 的自定义异常,专门用于表示网络连接超时的错误。

创建用户自定义异常通常是通过继承 Python 内置的 Exception 类或其子类来实现。

class MyCustomException(Exception):
    def __init__(self, message):
        self.message = message
​
    def __str__(self):
        return self.message
​
# 在函数中使用自定义异常
def do_something():
    if some_condition:
        raise MyCustomException("这是一个自定义的异常消息")

这段代码首先定义了一个名为 MyCustomException 的自定义异常类,它继承自 Python 内置的 Exception 类。

在自定义异常类中,__init__ 方法用于初始化异常对象的 message 属性,__str__ 方法用于定义异常对象的字符串表示形式。

然后,定义了一个名为 do_something 的函数。在这个函数中,如果 some_condition 为真,就会抛出 MyCustomException 类型的异常,并传递一个自定义的错误消息 "这是一个自定义的异常消息"

这样,在调用 do_something 函数时,如果触发了异常抛出的条件,就可以通过异常处理机制来捕获和处理这个自定义的异常。

6、定义清理行为

清理行为在实际应用中有众多重要场景,以下列举几个常见的:

  1. 文件和资源管理:

    • 当处理文件读写时,完成操作后关闭文件句柄,释放系统资源。

    • 类似地,对于数据库连接、网络套接字等资源,使用完毕后及时关闭和释放,以提高资源利用率和避免资源泄漏。

  2. 内存管理:

    • 在动态分配内存(如使用 malloc 或类似机制)后,如果不再使用,及时释放内存,防止内存泄漏导致程序性能下降甚至崩溃。

  3. 临时数据清理:

    • 程序运行过程中产生的临时文件、缓存数据等,在不再需要时删除,节省存储空间。

  4. 锁和同步机制:

    • 获得的锁在使用完毕后及时释放,以避免其他线程或进程被阻塞。

  5. 图形界面编程:

    • 比如创建的窗口、图形对象等,在程序结束或不再需要时进行正确的销毁和清理。

  6. 外部设备操作:

    • 与外部设备(如打印机、扫描仪等)进行通信后,确保设备连接正常关闭和资源释放。

举例:

def process_file(file_path):
    file = open(file_path, 'r')
    try:
        # 处理文件的逻辑
        data = file.read()
        # 对数据进行操作
    except Exception as e:
    #把 Exception 作为 一个异常输出
        # 处理异常
    finally:
        file.close()  # 确保文件被关闭

[Exception]  这个来说是不同异常类的总称,他可以是比如文件不存在、权限问题等问题的异常

7、预定义清理行为

在 Python 中,try-except-finally结构中的finally子句通常用于定义预定义的清理行为。

无论try子句中是否发生异常,也不管异常是否被except子句捕获,finally子句中的代码都会被执行。这使得finally子句非常适合用于执行一些必须进行的清理操作,比如关闭文件、释放资源(如数据库连接、网络连接等)、解锁互斥量等。

def process_file(file_path):
    file = open(file_path, 'r')
    try:
        # 处理文件的逻辑
        data = file.read()
        # 对数据进行操作
    except Exception as e:
    #把 Exception 作为 一个异常输出
        # 处理异常
    finally:
        file.close()  # 确保文件被关闭

例如在上述代码中,finally块中的file.close()函数,就是预定义的无论如何都会执行的清理行为(或者说操作)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值