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

时间:2010-02-13 19:43:14

标签: python console persistence terminal console-application

这类问题有很多变种。但是我特别想办法防止Python中的控制台应用程序在没有从终端(或其他控制台调用,因为它可能在Windows上调用)时调用。可能出现这种情况的一个示例是双击Windows资源管理器中的.py文件。

通常我使用类似下面的代码片段,但即使从现有终端调用应用程序,它也会产生令人遗憾的副作用:

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

它还假设所有Windows用户都从Windows“shell”调用该应用程序,并且只有Windows用户才能从现有终端以外的位置执行该程序。

是否有(最好是跨平台)方式来检测我的应用程序是否已从终端调用,和/或是否有必要为当前运行的实例提供“按任意键...”功能?请注意,使用批处理,bash或任何其他“包装器进程”解决方法是非常不受欢迎的。

Update0

使用下面的Alex Martelli's答案,我已经制作了这个功能:

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()

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

UPDATE1

在使用pywin32的过程中,我已经使用接受的答案生成了这个函数,该函数改进了上面的函数。注释掉的代码是源自Update0的替代实现。如果不能使用pywin32,请点击accepted answer中的链接。 Pause或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 个答案:

答案 0 :(得分:6)

首先,试图阻止你从聪明的黑客入手。设置一个单独的快捷方式是非常合适的,该快捷方式设计为从资源管理器运行,从命令行使用的脚本中执行稍微不同的操作(例如保持控制台打开)。正如Alex已经指出的那样,这不是nix上的一个问题,而且正确的做法总是干净利落,或者你的用户会抱怨。

如果你还想要一个解决方法,这里的code to detect when the console needs to be prevented from closing相当干净。需要Windows 2000或更高版本,逻辑包含在此函数中:

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

基本上,它获取拥有Python正在使用的控制台的进程的PID,以及我们的进程。如果它们是相同的,那么当我们退出控制台时会消失,所以它需要保持打开状态。如果它们不同,或者没有连接控制台,Python应该正常退出。

答案 1 :(得分:3)

在Unix上,sys.stdin.isatty()可靠地告诉您标准输入是来自类似终端的设备(或以其他方式重定向),同样适用于sys.stdoutsys.stderr上的相同方法 - 这样您就可以使用这些调用来确定应用程序是以交互方式执行还是在某些非交互式环境中执行(例如cron作业)。具体如何使用它们取决于你想要做什么如果(例如)标准输入和输出都被重定向到非终端但标准错误 转到终端 - 考虑每个在8个可能性中,从所有这些可能重定向到非终端到没有终端,并决定在每种情况下你想做什么。

在Windows上,情况有所不同,因为执行.py文件(而不是.pyw文件)将创建一个新的临时控制台(在Unix中没有完全相同的情况);我认为你想要处理的是这种情况? (或者它只是将标准I / O流重定向到文件,这在Windows中大致就像在Unix中一样?)。我认为Windows中最好的方法可能是使用win32api.SetConsoleCtrlHandlerCTRL_CLOSE_EVENT这样的事件设置处理程序 - 这样就应该调用处理程序(在这种情况下,当控制台关闭时) 进程的控制台,但不是。或者,如果您关心的是控制台是否完全存在(并且更愿意按照您的方式处理),请尝试在try的{​​{1}}段中调用win32api.GetConsoleTitle / try语句 - 如果没有控制台,它将生成一个异常(通过将你的布尔变量设置为except来捕获和响应),并且只是工作(在这种情况下你设置了布尔变量为False)如果控制台。