如何防止控制台应用在未从现有终端调用时关闭?
这类问题有很多不同的版本。不过我特别想知道如何让一个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 个回答
在Unix系统中,sys.stdin.isatty()
可以可靠地告诉你标准输入是否来自一个终端设备(或者是否被重定向了),同样的道理也适用于sys.stdout
和sys.stderr
这两个方法。你可以用这些方法来判断你的程序是以交互方式运行,还是在某种非交互的环境中运行(比如定时任务)。你想怎么使用这些方法,取决于你希望在不同情况下做什么。例如,如果标准输入和输出都被重定向到非终端设备,但标准错误输出却是指向终端的,你需要考虑这8种可能性,从全部重定向到非终端,到都没有重定向,然后决定在每种情况下你想怎么处理。
在Windows系统中情况就不一样了,因为执行一个.py
文件(与.pyw
文件不同)会创建一个新的临时控制台(在Unix中没有完全相同的情况)。我想你可能想处理的就是这种情况?(或者只是关于将标准输入输出流重定向到文件,这在Windows中和Unix大致相同?)。我认为在Windows中最好的方法可能是使用win32api.SetConsoleCtrlHandler来设置一个处理程序,用于处理像CTRL_CLOSE_EVENT
这样的事件——这样的话,如果进程有控制台,处理程序会在控制台关闭时被调用,但如果没有控制台则不会被调用。或者,如果你只关心控制台是否存在(并且希望以自己的方式处理其他情况),可以在try
部分的try
/except
语句中调用win32api.GetConsoleTitle——如果没有控制台,它会产生一个异常(你可以捕获这个异常并将你的布尔变量设置为False
),而如果有控制台,它就会正常工作(在这种情况下,你将那个布尔变量设置为True
)。
首先,我想劝你不要使用那些聪明的黑科技。其实,创建一个专门从资源管理器运行的快捷方式是完全可以的,这个快捷方式可以做一些和命令行脚本不一样的事情,比如保持控制台窗口打开。正如Alex已经提到的,这在类Unix系统上不是问题,正确的做法是总是干净地退出,否则用户会抱怨。
如果你还是想找个变通办法,这里有一段代码可以检测控制台何时需要防止关闭,这段代码相对简单。需要Windows 2000或更高版本,逻辑在这个函数里:
def owns_console():
wnd = GetConsoleWindow()
if wnd is None:
return False
return GetCurrentProcessId() == GetWindowThreadProcessId(wnd)
简单来说,它获取了拥有控制台的进程的PID(进程标识符)和我们自己进程的PID。如果它们是一样的,那么当我们退出时,控制台就会消失,所以需要保持它打开。如果它们不同,或者没有连接控制台,Python就应该正常退出。