欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

在Python中如何处理程序异常和用户强制退出的情况

最编程 2024-01-15 15:38:55
...

首先来谈下,我们为什么需要去捕捉Python程序的异常退出以及用户强制退出?

我们先通过一个场景来说明,假设我们写了一个不带界面的Python程序,其中包含调用某个硬件设备的代码,如:Tsmaster的TC1016, 一般硬件的调用的流程都比较类似;调用前需要做一下接口的初始化,随后调用各功能,调用好程序退出前,我们需要释放硬件资源为下一次调用做准备。

那么问题来了,如果程序退出前,没有做释放硬件资源的动作会怎么样呢? 答案是不确定的,有些功能较完善的硬件驱动,包含了自复位的功能,那么有可能退出前不释放硬件资源也OK,但有些驱动则不然,一旦没有释放资源,下次就无法再初始化了,只有拔USB或重启电脑可以解决。

那么你又会问,正常情况,我在程序退出前,加一步操作释放硬件资源不就可以解决这个问题了吗?
确实,这样可以保证正常情况下的资源释放,但如果程序本身发生异常时自动退出,不就有可能没有走到释放硬件资源的代码吗?
另外,如下图,用户直接通过打包后的程序的 X 按钮关闭程序的情况,怎么释放硬件资源呢?


关闭.png

这篇文章,主要就围绕这两点特殊情况展开。

首先,程序发生异常时自动退出的情况,我们可以使用Python自带的atexit函数来实现捕捉,
atexit的实现原理是,先通过atexit来注册退出程序时的回调函数self.stop1,一旦atexit捕获到程序因为异常或正常退出时,就会自动执行self.stop1中的发beep音的动作。

下面是一个简单的实例:

import atexit  #导入atexit  模块
import winsound  #beep音模块
from time import sleep

class job:

    def __init__(self):
        atexit.register(self.stop1)  #通过atexit注册退出程序时的回调函数,这个回调函数在解释器终止时自动执行
    
    def run(self):
        sleep(777)

    def stop1(self):
        winsound.Beep(800, 100)  #发出800hz的beep音
        print("closed1")

job=job()
job.run()

执行这段程序,当程序跑到sleep(777)这部分等待代码时,我们在pycharm中按下停止按钮或在终端中手动按下Crtl+C,来模拟一个KeyboardInterrupt 异常让程序终止。
我们观察到程序停止前发出了一次Beep音,并打印了closed1,也就是说程序的异常退出时被atexit 捕捉到,并调用了self.stop1子函数。
事实上除了KeyboardInterrupt 异常外,atexit 也可以捕捉其他各类Python的异常,以及sys.exit(), os._exit()等退出的情况。

所以今后,我们可以通过atexit来捕捉程序本身发生异常时的自动退出。接下来回到之前的话题,用户如果直接通过终端界面中的 X 按钮关闭程序,atexit是否也能捕捉到呢?
我们为此做了一个实验,将上面的程序打包成了exe,然后模拟用户在程序运行时直接通过终端界面中的 X 按钮关闭程序,结果发现程序没有发出任何beep音就直接关了。

通过实验我们证明了atexit 对于 X 按钮关闭程序方法也是无能为力,那么有没有方法能捕获到这种情况呢,答案是肯定的。
经过研究和实验,我们又发现了一种新的方法,我们可以通过pywin32库来调用SetConsoleCtrlHandler函数实现对终端窗口关闭的检测和捕获。
SetConsoleCtrlHandler的实现方法和atexit类似的,通过注册回调函数self.on_exit来实现控制台退出的捕获和处理。

我们还是用一个例子来演示。

import win32api
import winsound
from time import sleep

class job:

    def __init__(self):
        win32api.SetConsoleCtrlHandler(self.on_exit, True)
       #通过pywin32的SetConsoleCtrlHandler注册控制台退出时的回调函数,这个回调函数在控制台退出时自动执行

    def run(self):
        sleep(777)

    def on_exit(self, signal_type):
        winsound.Beep(800, 100)
        print('caught signal:', str(signal_type))

job=job()
job.run()

我们实际验证下效果,通过Pyinstaller打包上面的程序为一个exe文件,随后运行它,并在运行过程中按下了 X 按钮关闭程序,程序关闭前,我们听到了一声beep音,代表SetConsoleCtrlHandler注册的回调函数是生效的。
至此,我们已经了解了,如何在Python中捕捉到程序因为自身异常而退出以及用户强制退出这两种情况了。

总结:

  1. 实际操作中,我们可以模仿范例,替换beep音部分代码为接口释放的代码。我们必须做好每次硬件调用后因为异常退出或强制关闭时的硬件释放操作,以防止调用失败或是内存泄漏等情况。
  2. 第二种SetConsoleCtrlHandler的方法虽然比atexit适用范围更广且更强大,但其需要安装第三方库支持pywin32(pip install pywin32)。

推荐阅读