如何防止控制台应用在未从现有终端调用时关闭?

8 投票
2 回答
8714 浏览
提问于 2025-04-15 19:14

这类问题有很多不同的版本。不过我特别想知道如何让一个Python控制台应用在不是从终端(或者在Windows上可能叫做其他控制台)启动时不自动关闭。比如说,当你在Windows资源管理器中双击一个.py文件时,就可能会出现这种情况。

通常我会使用下面的代码片段,但它有个不太好的副作用,就是即使应用是从一个已经打开的终端启动的,它也会执行:

def press_any_key():
    if os.name == "nt":
        os.system("pause")
atexit.register(press_any_key)

这个方法还假设所有Windows用户都是从Windows的“命令行”启动应用,并且只有Windows用户才能从一个不是已经打开的终端的位置执行程序。

有没有一种(最好是跨平台的)方法可以检测我的应用是否是从终端启动的,或者是否需要为当前运行的实例提供“按任意键继续...”的功能?需要注意的是,使用批处理、bash或者其他“包装进程”的变通方法是非常不希望的。

更新0

根据Alex Martelli的回答,我写了这个函数:

def register_pause_before_closing_console():
    import atexit, os
    if os.name == 'nt':
        from win32api import GetConsoleTitle
        if not GetConsoleTitle().startswith(os.environ["COMSPEC"]):
            atexit.register(lambda: os.system("pause"))

if __name__ == '__main__':
    register_pause_before_closing_console()

如果有其他合适的答案出现,我会为其他平台和桌面环境添加更多代码。

更新1

在使用pywin32的思路下,我写了这个函数,它在上面的基础上进行了改进,使用了被接受的答案。注释掉的代码是来自更新0的另一种实现。如果不能使用pywin32,可以参考被接受的答案中的链接。可以根据需要使用暂停或getch()。

def _current_process_owns_console():
    #import os, win32api
    #return not win32api.GetConsoleTitle().startswith(os.environ["COMSPEC"])

    import win32console, win32process
    conswnd = win32console.GetConsoleWindow()
    wndpid = win32process.GetWindowThreadProcessId(conswnd)[1]
    curpid = win32process.GetCurrentProcessId()
    return curpid == wndpid

def register_pause_before_closing_console():
    import atexit, os, pdb
    if os.name == 'nt':
        if _current_process_owns_console():
            atexit.register(lambda: os.system("pause"))

if __name__ == '__main__':
    register_pause_before_closing_console()

2 个回答

3

在Unix系统中,sys.stdin.isatty()可以可靠地告诉你标准输入是否来自一个终端设备(或者是否被重定向了),同样的道理也适用于sys.stdoutsys.stderr这两个方法。你可以用这些方法来判断你的程序是以交互方式运行,还是在某种非交互的环境中运行(比如定时任务)。你想怎么使用这些方法,取决于你希望在不同情况下做什么。例如,如果标准输入和输出都被重定向到非终端设备,但标准错误输出却是指向终端的,你需要考虑这8种可能性,从全部重定向到非终端,到都没有重定向,然后决定在每种情况下你想怎么处理。

在Windows系统中情况就不一样了,因为执行一个.py文件(与.pyw文件不同)会创建一个新的临时控制台(在Unix中没有完全相同的情况)。我想你可能想处理的就是这种情况?(或者只是关于将标准输入输出流重定向到文件,这在Windows中和Unix大致相同?)。我认为在Windows中最好的方法可能是使用win32api.SetConsoleCtrlHandler来设置一个处理程序,用于处理像CTRL_CLOSE_EVENT这样的事件——这样的话,如果进程有控制台,处理程序会在控制台关闭时被调用,但如果没有控制台则不会被调用。或者,如果你只关心控制台是否存在(并且希望以自己的方式处理其他情况),可以在try部分的try/except语句中调用win32api.GetConsoleTitle——如果没有控制台,它会产生一个异常(你可以捕获这个异常并将你的布尔变量设置为False),而如果有控制台,它就会正常工作(在这种情况下,你将那个布尔变量设置为True)。

6

首先,我想劝你不要使用那些聪明的黑科技。其实,创建一个专门从资源管理器运行的快捷方式是完全可以的,这个快捷方式可以做一些和命令行脚本不一样的事情,比如保持控制台窗口打开。正如Alex已经提到的,这在类Unix系统上不是问题,正确的做法是总是干净地退出,否则用户会抱怨。

如果你还是想找个变通办法,这里有一段代码可以检测控制台何时需要防止关闭,这段代码相对简单。需要Windows 2000或更高版本,逻辑在这个函数里:

def owns_console():
    wnd = GetConsoleWindow()
    if wnd is None:
        return False
    return GetCurrentProcessId() == GetWindowThreadProcessId(wnd)

简单来说,它获取了拥有控制台的进程的PID(进程标识符)和我们自己进程的PID。如果它们是一样的,那么当我们退出时,控制台就会消失,所以需要保持它打开。如果它们不同,或者没有连接控制台,Python就应该正常退出。

撰写回答