由于某些其他原因,我使用的c ++共享库将一些文本输出到标准输出.在python中,我想捕获输出并保存到变量.关于重定向标准输出有许多类似的问题,但在我的代码中不起作用.
示例:禁止在库外调用模块的输出
1 import sys
2 import cStringIO
3 save_stdout = sys.stdout
4 sys.stdout = cStringIO.StringIO()
5 func()
6 sys.stdout = save_stdout
Run Code Online (Sandbox Code Playgroud)
在第5行,func()将调用共享库,共享库生成的文本仍然输出到控制台!如果改变func()打印"你好",它的工作原理!
我的问题是:
Ada*_*eld 17
Python的sys.stdout对象只是在通常的stdout文件描述符之上的Python包装器 - 更改它只影响Python进程,而不影响底层文件描述符.任何非Python代码,无论是其他可执行文件exec还是已加载的C共享库,都无法理解,并将继续使用I/O的普通文件描述符.
因此,为了使共享库输出到不同的位置,您需要通过打开新的文件描述符然后使用替换stdout来更改基础文件描述符os.dup2().您可以使用临时文件进行输出,但最好使用创建的管道os.pipe().但是,如果没有任何东西正在读取管道,这就有死锁的危险,所以为了防止我们可以使用另一个线程来排空管道.
下面是一个完整的工作示例,它不使用临时文件,并且不容易死锁(在Mac OS X上测试).
C共享库代码:
// test.c
#include <stdio.h>
void hello(void)
{
printf("Hello, world!\n");
}
Run Code Online (Sandbox Code Playgroud)
编译为:
$ clang test.c -shared -fPIC -o libtest.dylib
Run Code Online (Sandbox Code Playgroud)
Python驱动程序:
import ctypes
import os
import sys
import threading
print 'Start'
liba = ctypes.cdll.LoadLibrary('libtest.dylib')
# Create pipe and dup2() the write end of it on top of stdout, saving a copy
# of the old stdout
stdout_fileno = sys.stdout.fileno()
stdout_save = os.dup(stdout_fileno)
stdout_pipe = os.pipe()
os.dup2(stdout_pipe[1], stdout_fileno)
os.close(stdout_pipe[1])
captured_stdout = ''
def drain_pipe():
global captured_stdout
while True:
data = os.read(stdout_pipe[0], 1024)
if not data:
break
captured_stdout += data
t = threading.Thread(target=drain_pipe)
t.start()
liba.hello() # Call into the shared library
# Close the write end of the pipe to unblock the reader thread and trigger it
# to exit
os.close(stdout_fileno)
t.join()
# Clean up the pipe and restore the original stdout
os.close(stdout_pipe[0])
os.dup2(stdout_save, stdout_fileno)
os.close(stdout_save)
print 'Captured stdout:\n%s' % captured_stdout
Run Code Online (Sandbox Code Playgroud)
Dev*_*ams 11
多亏了很好的答案由亚当,我能得到这个工作.他的解决方案并不适合我的情况,因为我需要多次捕获文本,恢复和捕获文本,所以我不得不做一些相当大的改动.此外,我想让它也适用于sys.stderr(具有其他流的潜力).
所以,这是我最终使用的解决方案(有或没有线程):
import os
import sys
import threading
import time
class OutputGrabber(object):
"""
Class used to grab standard output or another stream.
"""
escape_char = "\b"
def __init__(self, stream=None, threaded=False):
self.origstream = stream
self.threaded = threaded
if self.origstream is None:
self.origstream = sys.stdout
self.origstreamfd = self.origstream.fileno()
self.capturedtext = ""
# Create a pipe so the stream can be captured:
self.pipe_out, self.pipe_in = os.pipe()
def __enter__(self):
self.start()
return self
def __exit__(self, type, value, traceback):
self.stop()
def start(self):
"""
Start capturing the stream data.
"""
self.capturedtext = ""
# Save a copy of the stream:
self.streamfd = os.dup(self.origstreamfd)
# Replace the original stream with our write pipe:
os.dup2(self.pipe_in, self.origstreamfd)
if self.threaded:
# Start thread that will read the stream:
self.workerThread = threading.Thread(target=self.readOutput)
self.workerThread.start()
# Make sure that the thread is running and os.read() has executed:
time.sleep(0.01)
def stop(self):
"""
Stop capturing the stream data and save the text in `capturedtext`.
"""
# Print the escape character to make the readOutput method stop:
self.origstream.write(self.escape_char)
# Flush the stream to make sure all our data goes in before
# the escape character:
self.origstream.flush()
if self.threaded:
# wait until the thread finishes so we are sure that
# we have until the last character:
self.workerThread.join()
else:
self.readOutput()
# Close the pipe:
os.close(self.pipe_in)
os.close(self.pipe_out)
# Restore the original stream:
os.dup2(self.streamfd, self.origstreamfd)
# Close the duplicate stream:
os.close(self.streamfd)
def readOutput(self):
"""
Read the stream data (one byte at a time)
and save the text in `capturedtext`.
"""
while True:
char = os.read(self.pipe_out, 1)
if not char or self.escape_char in char:
break
self.capturedtext += char
Run Code Online (Sandbox Code Playgroud)
使用sys.stdout,默认值:
out = OutputGrabber()
out.start()
library.method(*args) # Call your code here
out.stop()
# Compare the output to the expected value:
# comparisonMethod(out.capturedtext, expectedtext)
Run Code Online (Sandbox Code Playgroud)
使用sys.stderr:
out = OutputGrabber(sys.stderr)
out.start()
library.method(*args) # Call your code here
out.stop()
# Compare the output to the expected value:
# comparisonMethod(out.capturedtext, expectedtext)
Run Code Online (Sandbox Code Playgroud)
在一个with街区:
out = OutputGrabber()
with out:
library.method(*args) # Call your code here
# Compare the output to the expected value:
# comparisonMethod(out.capturedtext, expectedtext)
Run Code Online (Sandbox Code Playgroud)
在使用Python 2.7.6的Windows 7和使用Python 2.7.6的Ubuntu 12.04上进行了测试.
要在Python 3中工作,请更改char = os.read(self.pipe_out,1)
为char = os.read(self.pipe_out,1).decode(self.origstream.encoding).
更简单地说,Py 库有一个StdCaptureFD捕获流文件描述符的函数,它允许捕获 C/C++ 扩展模块的输出(与其他答案类似的机制)。请注意,据说该库仅处于维护状态。
>>> import py, sys
>>> capture = py.io.StdCaptureFD(out=False, in_=False)
>>> sys.stderr.write("world")
>>> out,err = capture.reset()
>>> err
'world'
Run Code Online (Sandbox Code Playgroud)
另一个值得注意的解决方案是,如果您在pytest测试装置中,则可以直接使用capfd,请参阅这些文档。
虽然其他答案也可能工作得很好,但我在 PyCharm IDE ( ) 中使用他们的代码时遇到了错误io.UnsupportedOperation: fileno,虽然StdCaptureFD工作得很好。
| 归档时间: |
|
| 查看次数: |
7908 次 |
| 最近记录: |