我正在处理来自MOOC的数据集。我有很多python3代码片段,我需要运行并从中获取结果。为此,我编写了一个循环遍历每个片段的python脚本。对于每个片段我:
sys.stdout
和sys.stderr
设置为我的stringIO缓冲区threading.thread
对象这适用于"正确"代码,但在其他情况下会出现问题:
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
答案 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}}参数。