在进程中安全地运行代码,在多线程.Process中重定向stdout

Zac*_*ack 7 python multithreading stdout python-3.x

我正在研究MOOC的数据集.我有很多python3代码片段,我需要运行并从中获取结果.为此,我编写了一个循环遍历每个片段的python脚本.对于每个片段我:

  1. 创建新的StringIO对象
  2. 设置sys.stdoutsys.stderr我的stringIO缓冲区
  3. threading.thread对象中执行代码段
  4. 加入主题
  5. 将结果记录在stringIO缓冲区中
  6. 恢复stdout和stderr

这适用于"正确"的代码,但在其他情况下这有问题:

  • 当代码具有无限循环时,thread.join不会终止该线程.该线程是一个守护程序线程,因此它在后台安静地运行,直到我的循环结束.
  • 当代码具有带a的无限循环时print(),当我将其设置回默认值(远离StringIO缓冲区)时,线程开始覆盖我的实际标准输出.这会污染我的报告.

这是我目前的代码:

def execCode(code, testScript=None):
    # create file-like string to capture output
    codeOut = io.StringIO()
    codeErr = io.StringIO()

    # capture output and errors
    sys.stdout = codeOut
    sys.stderr = codeErr

    def worker():
        exec(code, globals())

        if testScript:
            # flush stdout/stderror
            sys.stdout.truncate(0)
            sys.stdout.seek(0)
            # sys.stderr.truncate(0)
            # sys.stderr.seek(0)
            exec(testScript)

    thread = threading.Thread(target=worker, daemon=True)
    # thread = Process(target=worker) #, stdout=codeOut, stderr=codeErr)
    thread.start()
    thread.join(0.5)  # 500ms

    execError = codeErr.getvalue().strip()
    execOutput = codeOut.getvalue().strip()

    if thread.is_alive():
        thread.terminate()
        execError = "TimeError: run time exceeded"

    codeOut.close()
    codeErr.close()

    # restore stdout and stderr
    sys.stdout = sys.__stdout__
    sys.stderr = sys.__stderr__

    # restore any overridden functions
    restoreBuiltinFunctions()

    if execError:
        return False, stripOuterException(execError)
    else:
        return True, execOutput
Run Code Online (Sandbox Code Playgroud)

为了处理这种情况,我一直在尝试使用multithreading.Process和/或contextlib.redirect_stdout在一个进程中运行代码(然后我可以调用process.terminate()),但是我没有成功捕获stdout/stderr.

所以我的问题是:如何从进程重定向或捕获stdout/stderr?或者,还有其他方法可以尝试运行并捕获任意代码的输出吗?

(是的,我知道这通常是一个坏主意;我在虚拟机中运行它,以防万一有恶意代码)

Python版本是3.5.3


更新

在我看来,在这种情况下有一点灵活性.我有一个函数,preprocess(code)它接受代码提交作为字符串并改变它.大多数情况下,我一直用它来使用正则表达式替换一些变量的值.

这是一个示例实现:

def preprocess(code):
    import re
    rx = re.compile('earlier_date\s*=\s*.+')
    code = re.sub(rx, "earlier_date = date(2016, 5, 3)", code)
    rx = re.compile('later_date\s*=\s*.+')
    code = re.sub(rx, "later_date = date(2016, 5, 24)", code)
    return code
Run Code Online (Sandbox Code Playgroud)

我可以使用预处理功能来帮助重定向STDOUT

igr*_*nis 3

在 Python 中与正在运行的进程通信并不简单。由于某种原因,您只能在子流程生命周期中执行一次。根据我的经验,最好运行一个线程来启动一个进程,并在超时后获取其输出并终止子进程。

就像是:

def subprocess_with_timeout(cmd, timeout_sec, stdin_data=None):
    """Execute `cmd` in a subprocess and enforce timeout `timeout_sec` seconds.

    Send `stdin_data` to the subprocess.

    Return subprocess exit code and outputs on natural completion of the subprocess.
    Raise an exception if timeout expires before subprocess completes."""
    proc = os.subprocess.Popen(cmd,
                        stdin=subprocess.PIPE,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE)
    timer = threading.Timer(timeout_sec, proc.kill)
    # this will terminate subprocess after timeout
    timer.start()

    # you will be blocked here until process terminates (by itself or by timeout death switch)
    stdoutdata, stderrdata = proc.communicate(stdin_data) 

    if timer.is_alive():
        # Process completed naturally - cancel timer and return exit code
        timer.cancel()
        return proc.returncode, stdoutdata, stderrdata
    # Process killed by timer - raise exception
    raise TimeoutError('Process #%d killed after %f seconds' % (proc.pid, timeout_sec))
Run Code Online (Sandbox Code Playgroud)

因此,运行一个调用subprocess_with_timeout. 它应该处理输入并保存结果。

另一个想法是使用网络服务器来执行 IPC。请参阅此链接