Close Python IDLE shell without prompt

2020-02-13 04:58发布

问题:

I am working on a script (script A), that needs to open a new Python IDLE shell, automatically run another script (script B) in it and then close it. The following code is what I use for this purpose:

import sys
sys.argv=['','-n','-t','My New Shell','-c','execfile("VarLoader.py")']
import idlelib.PyShell
idlelib.PyShell.main()

However I can't get the new shell close automatically. I have tried adding the following to script B but either it doesn't close the new shell or a windows pops up asking whether I want to kill it.

exit()

.

import sys
sys.exit()

回答1:

Instead of monkeypatching or modifying the IDLE source code to make your program skip the prompt to exit I'd recommend you create a subclass of PyShell that overrides the close method how you want it to work:

import idlelib.PyShell
class PyShell_NoExitPrompt(idlelib.PyShell.PyShell):
    def close(self):
        "Extend EditorWindow.close(), does not prompt to exit"
##        if self.executing:
##            response = tkMessageBox.askokcancel(
##                "Kill?",
##                "Your program is still running!\n Do you want to kill it?",
##                default="ok",
##                parent=self.text)
##            if response is False:
##                return "cancel"
        self.stop_readline()
        self.canceled = True
        self.closing = True
        return idlelib.PyShell.EditorWindow.close(self)

The original issue with this was that then using idlelib.PyShell.main would not use your subclass, you can in fact create a copy of the function - without modifying the original - by using the FunctionType constructor that will use your modified class:

import functools
from types import FunctionType

def copy_function(f, namespace_override):
    """creates a copy of a function (code, signature, defaults) with a modified global scope"""
    namespace = dict(f.__globals__)
    namespace.update(namespace_override)
    new_f = FunctionType(f.__code__, namespace, f.__name__, f.__defaults__, f.__closure__)
    return functools.update_wrapper(f, new_f)

Then you can run your extra IDLE shell like this:

import sys
#there is also a way to prevent the need to override sys.argv but that isn't as concerning to me.
sys.argv = ['','-n','-t','My New Shell','-c','execfile("VarLoader.py")']
hacked_main = copy_function(idlelib.PyShell.main,
                            {"PyShell":PyShell_NoExitPrompt})

hacked_main()

Now you can leave IDLE the way it is and have your program work the way you want it too. (it is also compatible with other versions of python!)