中止对python交互式控制台的评估

时间:2017-02-24 17:50:34

标签: python standard-library

我正在编写自己的python代码编辑器和终端以获得乐趣,并在现有程序中实现它以增加可写性。

现在我发现了一个问题,即我不知道如何在代码运行后停止对代码的评估。怎么可能这样做?

这是我的实施:

import code
import contextlib
import sys
from io import StringIO
import copy


@contextlib.contextmanager
def capture():
    oldout,olderr = sys.stdout, sys.stderr
    try:
        out=[StringIO(), StringIO()]
        sys.stdout,sys.stderr = out
        yield out
    finally:
        sys.stdout,sys.stderr = oldout, olderr
        out[0] = out[0].getvalue()
        out[1] = out[1].getvalue()


class PythonTerminal(code.InteractiveConsole):

    def __init__(self, shared_vars):
        self.shared_vars_start = copy.deepcopy(shared_vars)
        self.shared_vars = shared_vars
        super().__init__(shared_vars)
        self.out_history = []

    def run_code(self,code_string):
        with capture() as out:
            self.runcode(code_string)

        self.out_history.append(out)
        return out

    def restart_interpreter(self):
        self.__init__(self.shared_vars_start)

    def stop(self):
        raise NotImplementedError

if __name__ == '__main__':
    a = range(10)
    PyTerm = PythonTerminal({'Betrag': a})
    test_code = """
for i in range(10000):
    for j in range(1000):
        temp = i*j
print('Finished'+str(i))
"""
    print('Starting')
    t = threading.Thread(target=PyTerm.run_code,args=(test_code,))
    t.start()

    PyTerm.stop()
    t.join()
    print(PyTerm.out_history[-1]) # This line should be executed immediately and contain an InterruptError

目标是评估停止,但解释器仍然存在,所以像ctrl + c。

2 个答案:

答案 0 :(得分:2)

尝试:

def stop(self):
    self.resetbuffer()#abort currently executing code by wiping the input buffer 
    self.push("exit()")#trigger an exit from the interpreter 

这两种方法的用法如下:

|  push(self, line)
|      Push a line to the interpreter.
|      
|      The line should not have a trailing newline; it may have
|      internal newlines.  The line is appended to a buffer and the
|      interpreter's runsource() method is called with the
|      concatenated contents of the buffer as source.  If this
|      indicates that the command was executed or invalid, the buffer
|      is reset; otherwise, the command is incomplete, and the buffer
|      is left as it was after the line was appended.  The return
|      value is 1 if more input is required, 0 if the line was dealt
|      with in some way (this is the same as runsource()).

 |  resetbuffer(self)
 |      Reset the input buffer.

答案 1 :(得分:1)

我认为你不能轻易地在Python中杀死一个线程。但是你可以杀死multiprocessing.Process。因此,您可以使用单独的流程在控制台中执行代码,并通过multiprocessing.Queue与其进行通信。为此,我实现了一个TerminalManager类,它可以在一个单独的进程中执行PythonTerminal.run_code并将其终止。请参阅下面的修改代码。一个主要的缺点是InteractiveConsole的本地人不会在调用之间保持不变。我添加了一个hack(可能很糟糕),将本地存储到搁置文件中。想到最快的事情。

import code
import contextlib
import sys
from io import StringIO
import copy
import threading
import multiprocessing
import json
import shelve

class QueueIO:
    """Uses a multiprocessing.Queue object o capture stdout and stderr"""
    def __init__(self, q=None):

        self.q = multiprocessing.Queue() if q is None else q

    def write(self, value):
        self.q.put(value)

    def writelines(self, values):
        self.q.put("\n".join(str(v) for v in values))

    def read(self):
        return self.q.get()

    def readlines(self):
        result = ""
        while not self.q.empty():
            result += self.q.get() + "\n"


@contextlib.contextmanager
def capture2(q: multiprocessing.Queue):
    oldout,olderr = sys.stdout, sys.stderr
    try:
        qio = QueueIO(q)
        out=[qio, qio]
        sys.stdout,sys.stderr = out
        yield out
    finally:
        sys.stdout,sys.stderr = oldout, olderr


class PythonTerminal(code.InteractiveConsole):

    def __init__(self, shared_vars):
        self.shared_vars_start = copy.deepcopy(shared_vars)
        self.shared_vars = shared_vars
        super().__init__(shared_vars)
        self.out_history = []

    def run_code(self,code_string, q):
        # retrieve locals
        d = shelve.open(r'd:\temp\shelve.pydb')
        for k, v in d.items():
            self.locals[k] = v

        # execute code
        with capture2(q) as out:
            self.runcode(code_string)            

        # store locals
        for k, v in self.locals.items():
            try:
                if k != '__builtins__':
                    d[k] = v
            except TypeError:
                pass
        d.close()


    def restart_interpreter(self):
        self.__init__(self.shared_vars_start)


class TerminalManager():

    def __init__(self, terminal):
        self.terminal = terminal
        self.process = None
        self.q = multiprocessing.Queue()        

    def run_code(self, test_code):
        self.process = multiprocessing.Process(
            target=self.terminal.run_code,args=(test_code, self.q))
        self.process.start()

    def stop(self):
        self.process.terminate()
        self.q.put(repr(Exception('User interupted execution.')))

    def wait(self):
        if self.process.is_alive:
            self.process.join()
        while not self.q.empty():
            print(self.q.get())    

if __name__ == '__main__':
    import time
    a = range(10)
    PyTerm = PythonTerminal({'Betrag': a})
    test_code = """
import time
a = 'hello'
for i in range(10):
    time.sleep(0.2)
    print(i)
print('Finished')
"""
    mgr = TerminalManager(PyTerm)
    print('Starting')
    mgr.run_code(test_code)    
    time.sleep(1)
    mgr.stop()
    mgr.wait()

    test_code = """
import time
_l = locals()

print('a = {}'.format(a))  
for i in range(10):
    time.sleep(0.1)
    print(i)
print('Finished')
"""
    print('Starting again')
    mgr.run_code(test_code)        

    mgr.wait()