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

发布于 2024-08-21 14:42:19 字数 2181 浏览 2 评论 0原文

这类问题有很多变体。然而,我特别寻求一种方法来防止 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 的 答案如下,我已经生成了此功能:

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 的脉络,我生成了 this 函数,它使用已接受的答案改进了上面的答案。注释掉的代码是源自 Update0 的替代实现。如果无法使用 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()

There are many variants on this kind of question. However I am specifically after a way to prevent a console application in Python from closing when it is not invoked from a terminal (or other console, as it may be called on Windows). An example where this could occur is double clicking a .py file from the Windows explorer.

Typically I use something like the following code snippet, but it has the unfortunate side effect of operating even if the application is invoked from an existing terminal:

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

It's also making the assumption that all Windows users are invoking the application from the Windows "shell", and that only Windows users can execute the program from a location other than an existing terminal.

Is there a (preferably cross platform) way to detect if my application has been invoked from a terminal, and/or whether it is necessary to provide a "press any key..." functionality for the currently running instance? Note that resorting to batch, bash or any other "wrapper process" workarounds are highly undesirable.

Update0

Using Alex Martelli's answer below, I've produced this function:

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

If other suitable answers arise, I'll append more code for other platforms and desktop environments.

Update1

In the vein of using pywin32, I've produced this function, which improves on the one above, using the accepted answer. The commented out code is an alternative implementation as originating in Update0. If using pywin32 is not an option, follow the link in the accepted answer. Pause or getch() to taste.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冬天旳寂寞 2024-08-28 14:42:19

首先,试图阻止您进行聪明的黑客攻击。拥有一个设计为从资源管理器运行的单独快捷方式是非常合适的,该快捷方式与从命令行使用的脚本执行的操作略有不同(例如保持控制台打开)。正如 Alex 已经指出的那样,这在 nix 上不是问题,正确的做法是彻底退出,否则用户会抱怨。

如果您仍然想要解决方法,请使用检测代码当需要阻止控制台关闭时 这是相当干净的。需要 Windows 2000 或更高版本,逻辑包含在此函数中:

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

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

First, an attempt to disuade you from clever hacks. It's perfectly appropriate to have a seperate shortcut designed to be run from Explorer that does slightly different things (like holding the console open) from the script to be used from the commandline. As Alex has already pointed out, this is not an issue on nix, and the right thing to do there is always exit cleanly or your users will complain.

If you still want a workaround, here's code to detect when the console needs to be prevented from closing that's reasonably clean. Requires Windows 2000 or later, the logic is contained in this function:

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

Basically, it gets the PIDs of the process that owns the console Python is using, and of our process. If they are the same, then when we exit the console will go away, so it needs to be held open. If they are different, or if there's no console attached, Python should exit normally.

树深时见影 2024-08-28 14:42:19

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

在 Windows 上,情况有所不同,因为执行 .py 文件(而不是 .pyw 文件)将创建一个新的瞬态控制台(Unix 中没有完全相同的情况) ;我想这就是您想要处理的情况? (或者只是将标准 I/O 流重定向到文件,这在 Windows 中大致就像在 Unix 中一样?)。我认为 Windows 中最好的方法可能是使用 win32api.SetConsoleCtrlHandlerCTRL_CLOSE_EVENT等事件设置处理程序——这样,如果进程有控制台,则应调用处理程序(在本例中,当控制台关闭时) ,但并非如此。或者,如果您关心的是控制台是否存在(并且更喜欢以其他方式处理事情),请尝试调用 win32api.GetConsoleTitle 位于 try/except 语句的 try 部分-- 如果没有控制台,它将生成一个异常(您可以通过将您的布尔变量设置为False来捕获并响应该异常),并且只需工作(在这种情况下,您将该布尔变量设置为< code>True) 如果有控制台。

On Unix, sys.stdin.isatty() reliably tells you whether standard input is coming from a terminal-like device (or is otherwise redirected), and similarly for the same method on sys.stdout and sys.stderr -- so you can use those calls to determine whether the application is being executed interactively or in some non-interactive environment (such as a cron job). Exactly how you want to use them depends on what you want to do if (for example) both standard input and output are redirected to a non-terminal but standard error is going to a terminal -- consider each of the 8 possibilities, from all of them redirected to non-terminals to none of them, and decide what you want to do in each case.

On Windows the situation is different since executing a .py file (as opposed to a .pyw file) will create a new transient console (there's no exactly equivalent situation in Unix); I assume that's the case you want to deal with? (Or is it just about redirection of standard I/O streams to files, which is possible in Windows roughly just like in Unix?). I think the best approach in Windows might be to use win32api.SetConsoleCtrlHandler to set a handler for such events as CTRL_CLOSE_EVENT -- this way the handler should be invoked (in this case, when the console closes) if there is a console for the process, but not otherwise. Or, if all you care about is whether a console is there at all or not (and prefer to handle things your way otherwise), try calling win32api.GetConsoleTitle in the try leg of a try/except statement -- it will generate an exception (which you catch and respond to by setting a boolean variable of yours to False) if there's no console, and just work (in which case you set that boolean variable to True) if there is a console.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文