Python句法的分享(五)

本文探讨了Python编程中异常处理的常见错误,强调了避免使用过于宽泛的except子句以防止干扰程序正常终止。同时,介绍了如何使用raisefrom语句链接异常,以提供更详细的错误信息。此外,还讲解了使用with语句作为上下文管理器的重要性,确保资源的正确获取和释放,防止资源泄漏。通过实例展示了在异常发生时仍能确保文件正确关闭的机制。
摘要由CSDN通过智能技术生成

10 避免except:子句带来的潜在问题

在异常处理中有一些常见的错误,这些错误可能导致程序无响应。

最常见的一种错误就是except:子句使用不当。如果不谨慎处理现有异常,那么可能还会出现其他错误。

本实例将介绍一些可以避免的常见异常处理错误。

10.1 准备工作

前面介绍了设计异常处理时的一些注意事项。该实例不鼓励使用BaseException,因为可能会干扰终止行为异常的Python程序。

本实例将扩展不该做什么(what not to do)思想。

10.2 实战演练

使用except Exception:语句作为最通用的异常管理。

处理过多的异常可能会干扰终止行为异常的Python程序的能力。当按下Ctrl + C,或通过kill -2发送SIGINT信号时,我们通常希望终止程序,而不是让程序写入消息并继续运行,或者完全停止响应。

其他应当谨慎处理的异常类包括:

SystemError
RuntimeError
MemoryError

这些异常通常意味着Python内核的某个地方出现了问题。与其静默这些异常或尝试恢复,不如终止程序,找到根本原因,并解决问题。

10.3 工作原理

在处理异常时应当注意以下两个问题。

不要捕获BaseException类。
不要使用不指明异常类的except: 语句。except: 语句将匹配所有异常,包括应当避免处理的异常。
使用except BaseException或不指明特定类的except语句可能导致程序在需要终止时变得无响应。

退一步讲,如果捕获了所有异常,那么还可以干扰这些内部异常的处理方式:

SystemExit
KeyboardInterrupt
GeneratorExit

如果静默、包装或重写这些异常,那么就可能引入新问题,甚至将一个简单的问题复杂化。

编写从不崩溃的程序是崇高的理想。干扰Python的某些内部异常并不会创建更可靠的程序。相反,明显的错误被掩盖了,这些错误变成了难解之谜。

11 使用raise from语句链接异常

在某些情况下,我们需要将一些看似不相关的异常合并为一个通用异常。对于复杂的模块来说,定义一个通用的Error异常是很常见的,该异常适用于模块中可能出现的多种情况。

在大多数情况下,通用异常都是必须的。如果抛出模块的Error异常,那么程序的某些功能将不能正常运行。

在极少数情况下,出于调试或监控的目的,我们希望得到异常的详细信息。我们也许想将这些详细信息写入日志,或者包含在电子邮件中。在这种情况下,我们需要提供放大或扩展通用异常的详细信息。可以通过将通用异常链接到根源异常来实现这种功能。

11.1 准备工作

假设我们正在编写一些复杂的字符串处理,希望将多种不同类型的详细异常视为一个通用异常,以便隔离软件用户与实施细节。我们可以将详细异常附加到通用异常。

11.2 实战演练

(1) 创建一个新的异常,如下所示:

class Error(Exception):
    pass

新的异常类就定义好了。

(2) 在处理异常时,可以使用raise from语句链接异常类,如下所示:

try:
    something
except (IndexError, NameError) as exception:
    print("Expected", exception)
    raise Error("something went wrong") from exception
except Exception as exception:
    print("Unexpected", exception)
    raise

第一个except子句匹配了两种类型的异常类。无论捕获到哪种类型的异常,都会从模块的通用Error异常类中抛出一个新的异常。新的异常将链接到根源异常。第二个except子句匹配了通用的Exception类。我们首先写入了一个日志消息,然后重新抛出了异常。第二个except子句没有链接异常,而是在另一个上下文中继续异常处理。

11.3 工作原理

Python异常类的__cause__属性可以记录异常的原因。可以使用raise Exception from Exception语句来设置__cause__属性。

抛出这种异常时的详细信息如下所示:

>>> class Error(Exception):
...     pass
>>> try:
...     'hello world'[99]
... except (IndexError, NameError) as exception:
...     raise Error("index problem") from exception
...
Traceback (most recent call last):
  File "<doctest default[0]>", line 2, in <module>
    'hello world'[99]
IndexError: string index out of range

上述异常是以下异常的直接原因:

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/ doctest.py", line 1318, in __run
    compileflags, 1), test.globs)
  File "<doctest default[0]>", line 4, in <module>
    raise Error("index problem") from exception
Error: index problem

上述示例展示了一个相互链接的异常。Traceback消息中的第一个异常是IndexError异常,这是直接原因。Traceback中的第二个异常是通用Error异常,这是一个链接到原始原因的通用汇总异常。

应用程序将在try: 语句中抛出Error异常。代码如下所示:

try:
    some_function()
except Error as exception:
    print(exception)
    print(exception .__cause__)

这个示例包含了一个名为some_function()的函数,它可以抛出通用的Error异常。如果该函数确实抛出异常,则except子句将匹配通用的Error异常。我们可以打印异常的消息exception,以及异常的根本原因exception.cause。在许多应用程序中,exception.__cause__值可能会被写入调试日志,而不是显示给用户。

11.4 补充知识

如果在异常处理代码中抛出异常,那么也会创建一种异常链接关系。这是一种上下文(context)关系,而不是原因(cause)关系。

上下文消息看起来都很相似,只是消息略有不同。这说明在处理上一个异常时,发生了另一个异常。第一个Traceback显示了原始异常。第二个消息是未使用显式连接抛出的异常。

通常,上下文是无计划的,只是表示except异常处理块中出现错误。例如:

try:
    something
except ValueError as exception:
    print("Some message", exceotuib)

上述示例通过ValueError异常的上下文抛出一个NameError异常。NameError异常源于异常变量被错误地拼写为exceotuib。

12 使用with语句管理上下文

在许多情况下,脚本需要使用外部资源,最常见的例子就是磁盘文件和与外部主机的网络连接。没有及时断开外部资源的绑定,导致资源无法释放是一种常见的bug。这种bug有时被称为内存泄漏,因为每次打开一个新文件时都不关闭以前使用的文件,所以导致可用内存减少。

我们想隔离资源的绑定,确保正确获取和释放资源,其中一种解决方法就是创建脚本使用外部资源的上下文。在上下文结束时,程序不再与资源绑定,我们希望确保资源被释放。

12.1 准备工作

假设我们想向一个CSV格式的文件写入几行数据。完成写入后,想要确保文件关闭,并释放各种操作系统资源(包括缓冲区和文件句柄)。上下文管理器可以实现这个设想,它可以确保文件正确关闭。

因为需要处理CSV文件,所以可以使用CSV模块处理格式化的细节:

>>> import csv

另外还需要使用pathlib模块查找待处理的文件:

>>> import pathlib

为了模拟写入,我们还准备了虚拟的数据源:

>>> some_source = [[2,3,5], [7,11,13], [17,19,23]]

上述代码提供了一个了解with语句的情景。

12.2 实战演练

(1) 通过打开文件或使用urllib.request.urlopen()创建网络连接创建上下文。其他常见的上下文包括zip文件和tar文件。

target_path = pathlib.Path('code/test.csv')
with target_path.open('w', newline='') as target_file:

(2) 添加所有处理过程,确保在with语句内缩进。

target_path = pathlib.Path('code/test.csv')
with target_path.open('w', newline='') as target_file:
    writer = csv.writer(target_file)
    writer.writerow(['column', 'data', 'headings'])
    for data in some_source:
        writer.writerow(data)

(3) 在使用文件作为上下文管理器时,文件将在缩进的上下文代码块执行结束后自动关闭。即使出现异常,文件仍然正常关闭。在关闭上下文并释放资源后,继续执行上下文代码块下面的处理。

target_path = pathlib.Path('code/test.csv')
with target_path.open('w', newline='') as target_file:

    writer = csv.writer(target_file)
    writer.writerow(['column', 'headings'])
    for data in some_source:
        writer.writerow(data)

print('finished writing', target_path)

with上下文之外的语句将在上下文关闭后执行,由target_path.open()打开的文件将正确关闭。

即使在with语句内部抛出异常,文件仍然正确关闭。虽然上下文管理器已经得到了异常信息,但是它仍然可以关闭文件并允许传播异常。

12.3 工作原理

代码通知上下文管理器退出的方式有两种:

没有异常,正常退出;
抛出异常。
在任何情况下,上下文管理器都将程序与外部资源分开,可以关闭文件、删除网络连接、提交或回滚数据库事务,以及释放锁。

可以通过在with语句内添加手动异常来进行验证。下面的代码可以证明文件正确关闭:

try:
    target_path = pathlib.Path('code/test.csv')
    with target_path.open('w', newline='') as target_file:
        writer = csv.writer(target_file)
        writer.writerow(['column', 'headings'])
        for data in some_source:
            writer.writerow(data)
            raise Exception("Just Testing")
except Exception as exc:
    print(target_file.closed)
    print(exc)
print('finished writing', target_path)

在本例中,核心处理包装在try语句中。因此,在第一次写入CSV文件之后就可以抛出异常。抛出异常时,可以打印输出异常。此时,文件也将被关闭。输出如下所示:

True
Just Testing
finished writing code/test.csv

这个例子说明了文件被正确关闭,另外还展示了异常相关的消息,以确认该异常是我们手动抛出的异常。输出文件test.csv只有从变量some_source中获取的第一行数据。

12.4 补充知识

Python提供了多种上下文管理器。打开的文件是一种上下文管理器,由urllib.request.urlopen()创建的网络连接同样也是一种上下文管理器。

对于所有文件操作和网络连接,都应该通过with语句用作上下文管理器。我们很难找到关于这一规则的一种例外情况。

实际上,因为decimal模块使用了上下文管理器,所以允许对小数运算的方法进行局部修改。可以使用decimal.localcontext()函数作为上下文管理器,改变由with语句隔离的舍入规则或精度。

还可以自定义上下文管理器。contextlib模块包含很多函数和装饰器,可以帮助我们根据资源创建上下文管理器,而不是显式地提供资源。

当使用锁时,with上下文管理器是获取和释放锁的理想方式。关于threading模块创建的锁对象与上下文管理器之间的关系,请参阅https://docs.python.org/3/library/threading.html#with-locks。

12.5 延伸阅读

关于with语句的起源,请参阅https://www.python.org/dev/peps/pep-0343/。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值