You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何检测Python脚本被用户关闭?适配Mac/Windows终端退出场景

嘿,刚好我之前处理过类似的需求——atexit确实只能覆盖正常退出、Ctrl+C这类“温和”的终止场景,遇到用户直接关终端、或者用任务管理器结束进程这种情况就抓瞎了。下面给你一套跨Mac和Windows的完整解决方案:

跨平台监测脚本退出的实现方案

1. Mac(类Unix系统):捕获系统信号

在Mac上,当用户关闭终端窗口时,系统会给终端内的进程发送SIGHUP信号;如果是通过kill命令正常终止进程(非强制的kill -9),进程会收到SIGTERM信号。我们可以用Python内置的signal模块来拦截这些信号,先执行清理逻辑再退出。

示例代码:

import signal
import atexit
import sys

def cleanup():
    print("脚本正在执行清理工作...")
    # 这里写你的具体清理逻辑:关闭文件句柄、释放网络连接、保存临时数据等

def signal_handler(signum, frame):
    # 收到信号后触发清理,然后优雅退出
    cleanup()
    sys.exit(0)

# 注册需要监听的信号
signal.signal(signal.SIGHUP, signal_handler)  # 捕获终端关闭事件
signal.signal(signal.SIGTERM, signal_handler) # 捕获正常终止命令
signal.signal(signal.SIGINT, signal_handler)  # 捕获Ctrl+C(可选,替代atexit的部分场景)

# 保留atexit作为兜底,处理正常退出的情况
atexit.register(cleanup)

# 模拟你的主业务逻辑
if __name__ == "__main__":
    print("脚本运行中...可以试试关闭终端或按Ctrl+C测试")
    while True:
        pass  # 这里替换成你的长时间运行任务

2. Windows系统:处理控制台关闭事件

Windows没有SIGHUP这类信号,当用户关闭控制台窗口、或者通过任务管理器结束控制台进程时,系统会发送CTRL_CLOSE_EVENT(还有登出、关机时的CTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT)。我们需要用ctypes调用Windows系统API来捕获这些事件。

示例代码:

import ctypes
import atexit
import sys

def cleanup():
    print("脚本正在执行清理工作...")
    # 你的清理逻辑

# Windows控制台事件的回调函数
def console_handler(event):
    # 0=Ctrl+C事件,2=控制台关闭事件
    if event in (0, 2):
        cleanup()
        return True  # 告诉系统我们已经处理了这个事件
    return False

# 注册控制台事件处理器
handler = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_uint)(console_handler)
ctypes.windll.kernel32.SetConsoleCtrlHandler(handler, True)

# 兜底的atexit注册
atexit.register(cleanup)

# 主业务逻辑
if __name__ == "__main__":
    print("脚本运行中...试试关闭控制台窗口测试")
    while True:
        pass

3. 一键兼容跨平台的整合版本

把上面的逻辑结合起来,写一个自动适配Mac和Windows的脚本,不用手动区分平台:

import atexit
import sys

def cleanup():
    print("执行清理操作...")
    # 此处添加你的清理代码:比如保存程序状态、关闭数据库连接等

# 根据平台选择对应的监听方式
if sys.platform == "win32":
    import ctypes
    def console_handler(event):
        if event in (0, 2):  # 处理Ctrl+C和控制台关闭事件
            cleanup()
            return True
        return False
    # 注册Windows控制台事件
    handler = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_uint)(console_handler)
    ctypes.windll.kernel32.SetConsoleCtrlHandler(handler, True)
else:
    import signal
    def signal_handler(signum, frame):
        cleanup()
        sys.exit(0)
    # 注册类Unix系统的信号监听
    signal.signal(signal.SIGHUP, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    signal.signal(signal.SIGINT, signal_handler)

# 兜底的正常退出处理
atexit.register(cleanup)

# 主程序逻辑
if __name__ == "__main__":
    print(f"脚本在{sys.platform}平台上运行中...")
    try:
        while True:
            # 你的业务代码写在这里
            pass
    except Exception as e:
        print(f"程序发生异常: {str(e)}")
        cleanup()
        sys.exit(1)

几个关键注意点

  • 无法处理强制终止:不管是Mac上的kill -9还是Windows上的“结束进程树”,这类强制终止是系统直接干掉进程,根本不给执行清理的机会——这是系统级的限制,没有任何办法绕过。
  • 清理逻辑要轻量化:处理信号或控制台事件时,系统留给进程的执行时间非常有限,所以清理代码要尽量简洁,避免复杂的IO操作或者耗时任务,否则可能还没执行完就被系统强制终止了。

内容的提问来源于stack exchange,提问作者Tron

火山引擎 最新活动