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

时间:2017-11-13 01:26:28

标签: python multithreading python-3.x stdout

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

  1. 创建新的StringIO对象
  2. sys.stdoutsys.stderr设置为我的stringIO缓冲区
  3. 执行threading.thread对象
  4. 中的代码段
  5. 加入主题
  6. 将结果记录在stringIO buffers
  7. 恢复stdout和stderr
  8. 这适用于"正确"代码,但在其他情况下会出现问题:

    • 当代码有无限循环时,thread.join不会杀死该线程。该线程是一个守护程序线程,因此它在后台安静地运行,直到我的循环结束。
    • 当代码具有带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
    

    要处理这种情况,我一直在尝试使用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
    

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

2 个答案:

答案 0 :(得分: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))

因此,运行一个调用subprocess_with_timeout的线程执行器。它应该处理输入并保存结果。

另一个想法是使用网络服务器来进行IPC。见this link

答案 1 :(得分:0)

subprocess.check_output怎么样?您可以使用它来调用python -c {snippet},或者如果它更长,只需将代码段写入临时.py文件即可。 check_output(事实上,subprocess中的所有其他功能)都有一个timeout参数。

一般的想法是:

import subprocess
import sys

def execCode(code):
    try:
        output = subprocess.check_output([sys.executable, '-c', code],
                                         stdin=subprocess.PIPE,
                                         stderr=subprocess.PIPE,
                                         timeout=0.5)
        return True, output
    except subprocess.TimeoutExpired as te:
        return False, 'run time exceeded'
    except subprocess.CalledProcessError as cpe:
        return False, cpe.stderr

示例在IPython中运行:

In [18]: execCode('import os\nprint(" ".join(os.listdir()))')
Out[18]:
(True,
 b'contents of directory\n')

In [19]: execCode('import time\ntime.sleep(1)')
Out[19]: (False, 'run time exceeded')

In [20]: execCode('import os\nprint("\t".join(os.listdi))')
Out[20]: 
(False,
 b'Traceback (most recent call last):\n  File "<string>", line 2, in <module>\nAttributeError: module \'os\' has no attribute \'listdi\'\n')

请注意,check_output会返回bytes序列,因此您必须将其转换为str。或者,您可以使用encoding的{​​{1}}参数。