【问题标题】:How to correctly handle SIGINT to close files/connections如何正确处理 SIGINT 以关闭文件/连接
【发布时间】:2014-12-10 07:40:47
【问题描述】:

我想在我的脚本中实现正确的 SIGINT 处理,它会打开多个文件和一个数据库连接。如果脚本是 CTRL+C'd 或以其他方式中断,则应关闭这些。

之前我使用KeyboardInterrupt 异常来捕获 CTRL+C,在那里我检查了文件/连接是否已定义,如果定义了,则关闭它们,然后退出。

这真的是pythonic的方式吗,还是建议使用信号处理程序更好?例如

import signal, sys, time

def handler(signum, frame):
    print("..kthxbye")
    sys.exit(1)

def main():
    signal.signal(signal.SIGINT, handler)
    i = 0
    while True:
        print(i)
        i += 1
        time.sleep(1)

if __name__ == "__main__":
    main()

这对我来说似乎更干净,但我不知道如何将文件名或数据库连接传递给处理程序。

【问题讨论】:

    标签: python signals exit interrupt-handling


    【解决方案1】:

    我宁愿在主线程上捕获KeyboardInterrupt 异常。 KeyboardInterrupt 是 python 的默认 SIGINT 处理程序的结果。 KeyboardInterrupt 异常的异常处理程序比直接捕获 SIGINT 时所处的环境更安全/友好。

    if __name__ == '__main__':
        try:
            main()
        except KeyboardInterrupt:
            cleanup()
    

    编辑:这是在两种方法之间共享变量(状态)的方法:

    程序:

    import sys, time
    
    class SharedState:
        def __init__(self):
            self.var0 = 42
            self.var1 = 'string'
    
    # method 1
    shared_variable = 'woof woof'
    
    # method 2: avoiding global declarations in functions
    shared_state = SharedState()
    
    def main():
        # In order to write a global variable you need a global
        # declaration otherwise the assignment would create a
        # local variable
    
        global shared_variable
        shared_variable = 5
    
        shared_state.var0 = 10
    
        time.sleep(10)
    
    def cleanup():
        print shared_variable
        print shared_state.var0
        sys.exit(1)
    
    if __name__ == '__main__':
        try:
            main()
        except KeyboardInterrupt:
            cleanup()
    

    面向对象(我的偏好):

    import sys, time
    
    # method 3: object oriented programming
    class Program:
        def __init__(self):
            self.var0 = 42
            self.var1 = 'string'
    
        def main(self):
            self.var0 = 5
            self.var1 = 'woof woof'
            time.sleep(10)
    
        def cleanup(self):
            # both main and cleanup can access the member
            # variables of this class
            print self.var0
            print self.var1
            sys.exit(1)
    
        def execute(self):
            try:
                self.main()
            except KeyboardInterrupt:
                self.cleanup()
    
    if __name__ == '__main__':
        Program().execute()
    

    【讨论】:

    • 感谢您的回答。所以我宁愿将我的几百行 main 包装在 try/except 中,例如:try: print("files and databases and stuff") except KeyboardInterrupt: print("clean-up")?
    • @DaedalusMythos 我从不喜欢长方法或长块(让它成为一个 try-except 块)。而是将主函数的全部内容放入另一个函数(例如:safe_main()),然后在主函数中,您只有一个简单的 try-except 结构,在其 try 块中只有一个函数调用。您可以类似地简化 except 块。例如,在 C++ 中,标准库以类似方式调用您的 main 函数,包装在 try-catch 块中。
    • @DaedalusMythos 好吧,我刚刚注意到您基本上已经有 2 个主要块...您可以在 if __name__ == "__main__": 块中轻松地尝试除法。那么你的 try 块只包含 main() 调用。请参阅我的更新答案。
    • 感谢更新!正如您所演示的那样,我已经考虑实现 try/except,但我不知道如何从 cleanup() 中的 main() 访问文件/数据库连接..
    • @DaedalusMythos 当然,您必须在main()cleanup() 之间共享一个变量/状态。您必须知道中断可能随时出现,因此cleanup() 必须准备好处理您的进程的任何可能状态。然而,它可能就像关闭一些文件或刷新一些队列一样简单。这取决于您的程序设计。
    【解决方案2】:

    我的建议是使用signal 库来处理信号。信号也不例外,它们是操作系统Inter Process Communication (IPC) 基础架构的一部分。

    信号可以帮助您与您的程序进行通信,例如重新加载配置文件、在日志轮换期间关闭您的日志文件处理程序等。像 apache 这样的大多数守护进程都会使用它。

    Shell 脚本有trap 命令来处理信号并根据捕获的信号采取适当的行动。

    通常python在退出时会自动关闭所有文件处理程序和数据库连接。但为了安全起见,我们可以有一个函数来隐式处理它们。

    以下代码捕获 SIGINT 并正确关闭文件。

    import signal
    import sys
    
    die = False
    
    def handler(signum, frame):
        global die
        print('Got SIGINT.')
        die = True
    
    def closeFile(fh):
        fh.flush()
        fh.close()
    
    signal.signal(signal.SIGINT, handler)
    
    fh = open('/tmp/a.txt', 'w')
    
    while True:
        data = input('> ')
    
        if data == 'q':
            closeFile(fh)
            break
        else:
            fh.write(data + '\n')
    
        if die:
            closeFile(fh)
            print('Completed cleanup.. ')
            sys.exit()
    

    【讨论】:

    • 感谢您的回答!如何将数据库连接/打开的文件传递给处理程序 - 以便在调用时关闭它?
    • 在 python 中你不能像在 C 程序中那样处理信号。当您的信号处理程序被调用时,C 信号处理程序已经返回。更多信息:docs.python.org/2/library/signal.html 通过直接处理SIGINT,您可能会冒着您的python 程序在一个操作中间被中断的风险,否则在异常处理方面被视为“原子”。您可以从异常处理程序安全地调用 python 方法,但不能从信号处理程序执行相同操作。除非信号,否则在“原子”操作之间会引发异常。
    • @DaedalusMythos 我添加了一个示例代码,用于在接收 SIGINT 和终止之前关闭文件处理程序。
    猜你喜欢
    • 2014-07-04
    • 2015-11-20
    • 1970-01-01
    • 2011-09-27
    • 1970-01-01
    • 1970-01-01
    • 2011-06-16
    • 2018-02-14
    • 2023-03-22
    相关资源
    最近更新 更多