![](/img/trans.png)
[英]In python, how to capture the stdout from a c++ shared library to a variable
[英]How do I prevent a C shared library to print on stdout in python?
我使用 python 库导入 C 共享库,在标准输出上打印。 我想要一个干净的 output 以便将它与管道一起使用或在文件中重定向。 打印是在 python 之外的共享库中完成的。
一开始,我的做法是:
# file: test.py
import os
from ctypes import *
from tempfile import mktemp
libc = CDLL("libc.so.6")
print # That's here on purpose, otherwise hello word is always printed
tempfile = open(mktemp(),'w')
savestdout = os.dup(1)
os.close(1)
if os.dup(tempfile.fileno()) != 1:
assert False, "couldn't redirect stdout - dup() error"
# let's pretend this is a call to my library
libc.printf("hello world\n")
os.close(1)
os.dup(savestdout)
os.close(savestdout)
第一种方法只奏效了一半:
- 出于某种原因,在移动标准输出之前它需要一个“print”语句,否则总是打印 hello word。 因此,它将打印一个空行,而不是库通常输出的所有模糊内容。
- 更烦人的是,重定向到文件时失败:
$python test.py > foo && cat foo
hello world
我的第二次 python 尝试的灵感来自评论中给出的另一个类似主题:
import os
import sys
from ctypes import *
libc = CDLL("libc.so.6")
devnull = open('/dev/null', 'w')
oldstdout = os.dup(sys.stdout.fileno())
os.dup2(devnull.fileno(), 1)
# We still pretend this is a call to my library
libc.printf("hello\n")
os.dup2(oldstdout, 1)
这个也无法阻止打印“hello”。
由于我觉得这有点低级,所以我决定 go 完全使用 ctypes。 我从这个 C 程序中得到灵感,它不打印任何东西:
#include <stdio.h>
int main(int argc, const char *argv[]) {
char buf[20];
int saved_stdout = dup(1);
freopen("/dev/null", "w", stdout);
printf("hello\n"); // not printed
sprintf(buf, "/dev/fd/%d", saved_stdout);
freopen(buf, "w", stdout);
return 0;
}
我构建了以下示例:
from ctypes import *
libc = CDLL("libc.so.6")
saved_stdout = libc.dup(1)
stdout = libc.fdopen(1, "w")
libc.freopen("/dev/null", "w", stdout);
libc.printf("hello\n")
libc.freopen("/dev/fd/" + str(saved_stdout), "w", stdout)
这会打印“你好”,即使我在 printf 之后使用 libc.fflush(stdout)。我开始认为可能无法在 python 中执行我想要的操作。或者我获取指向 stdout 的文件指针的方式可能是不对。
你怎么认为?
基于@Yinon Ehrlich 的回答。 此变体试图避免泄漏文件描述符:
import os
import sys
from contextlib import contextmanager
@contextmanager
def stdout_redirected(to=os.devnull):
'''
import os
with stdout_redirected(to=filename):
print("from Python")
os.system("echo non-Python applications are also supported")
'''
fd = sys.stdout.fileno()
##### assert that Python and C stdio write using the same file descriptor
####assert libc.fileno(ctypes.c_void_p.in_dll(libc, "stdout")) == fd == 1
def _redirect_stdout(to):
sys.stdout.close() # + implicit flush()
os.dup2(to.fileno(), fd) # fd writes to 'to' file
sys.stdout = os.fdopen(fd, 'w') # Python writes to fd
with os.fdopen(os.dup(fd), 'w') as old_stdout:
with open(to, 'w') as file:
_redirect_stdout(to=file)
try:
yield # allow code to be run with the redirected stdout
finally:
_redirect_stdout(to=old_stdout) # restore stdout.
# buffering and flags such as
# CLOEXEC may be different
是的,你真的想使用os.dup2
而不是os.dup
,就像你的第二个想法。 您的代码看起来有些迂回。 除了/dev/null
之外,不要与/dev
条目/dev/null
,这是不必要的。 在这里也没有必要用 C 写任何东西。
诀窍是使用dup
保存stdout
fdes,然后将其传递给fdopen
以创建新的sys.stdout
Python 对象。 同时,打开一个 fdes 到/dev/null
并使用dup2
覆盖现有的stdout
fdes。 然后将旧的 fdes 关闭到/dev/null
。 调用dup2
是必要的,因为我们无法告诉open
我们希望它返回哪些 fdes, dup2
实际上是唯一的方法。
编辑:如果你重定向到一个文件,那么 stdout 不是行缓冲的,所以你必须刷新它。 你可以从 Python 中做到这一点,它会正确地与 C 互操作。 当然,如果您在向stdout
写入任何内容之前调用此函数,那么这无关紧要。
这是我刚刚测试过的一个例子,它适用于我的系统。
import zook
import os
import sys
def redirect_stdout():
print "Redirecting stdout"
sys.stdout.flush() # <--- important when redirecting to files
newstdout = os.dup(1)
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, 1)
os.close(devnull)
sys.stdout = os.fdopen(newstdout, 'w')
zook.myfunc()
redirect_stdout()
zook.myfunc()
print "But python can still print to stdout..."
“zook”模块是一个非常简单的 C 库。
#include <Python.h>
#include <stdio.h>
static PyObject *
myfunc(PyObject *self, PyObject *args)
{
puts("myfunc called");
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef zookMethods[] = {
{"myfunc", myfunc, METH_VARARGS, "Print a string."},
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC
initzook(void)
{
(void)Py_InitModule("zook", zookMethods);
}
和输出?
$ python2.5 test.py
myfunc called
Redirecting stdout
But python can still print to stdout...
并重定向到文件?
$ python2.5 test.py > test.txt
$ cat test.txt
myfunc called
Redirecting stdout
But python can still print to stdout...
结合这两个答案 - https://stackoverflow.com/a/5103455/1820106 & https://stackoverflow.com/a/4178672/1820106到上下文管理器,该上下文管理器仅在其范围内阻止打印到标准输出(第一个答案中的代码阻止了任何外部输出,后一个答案错过了最后的 sys.stdout.flush()):
class HideOutput(object):
'''
A context manager that block stdout for its scope, usage:
with HideOutput():
os.system('ls -l')
'''
def __init__(self, *args, **kw):
sys.stdout.flush()
self._origstdout = sys.stdout
self._oldstdout_fno = os.dup(sys.stdout.fileno())
self._devnull = os.open(os.devnull, os.O_WRONLY)
def __enter__(self):
self._newstdout = os.dup(1)
os.dup2(self._devnull, 1)
os.close(self._devnull)
sys.stdout = os.fdopen(self._newstdout, 'w')
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout = self._origstdout
sys.stdout.flush()
os.dup2(self._oldstdout_fno, 1)
这是我最终做到的。 我希望这对其他人有用(这适用于我的 linux 站)。
我自豪地介绍了 libshutup,它旨在让外部库关闭。
1)复制以下文件
// file: shutup.c
#include <stdio.h>
#include <unistd.h>
static char buf[20];
static int saved_stdout;
void stdout_off() {
saved_stdout = dup(1);
freopen("/dev/null", "w", stdout);
}
void stdout_on() {
sprintf(buf, "/dev/fd/%d", saved_stdout);
freopen(buf, "w", stdout);
}
2) 编译为共享库
gcc -Wall -shared shutup.c -fPIC -o libshutup.so
3)像这样在你的代码中使用它
from ctypes import *
shutup = CDLL("libshutup.so")
shutup.stdout_off()
# Let's pretend this printf comes from the external lib
libc = CDLL("libc.so.6")
libc.printf("hello\n")
shutup.stdout_on()
这里的最高答案非常好。 但是,它需要sys.stdout.close()
与 Juypter 冲突,如果有人使用 Python 笔记本。 有一个很棒的项目叫做 Wurlitzer,它通过上下文管理器解决了底层问题,不仅可以在 Jupter 中使用,而且还提供了一个原生的 Jupyer 扩展。
https://github.com/minrk/wurlitzer
https://pypi.org/project/wurlitzer/
pip install wurlitzer
from wurlitzer import pipes
with pipes() as (out, err):
call_some_c_function()
stdout = out.read()
from io import StringIO
from wurlitzer import pipes, STDOUT
out = StringIO()
with pipes(stdout=out, stderr=STDOUT):
call_some_c_function()
stdout = out.getvalue()
from wurlitzer import sys_pipes
with sys_pipes():
call_some_c_function()
最神奇的部分:它支持 Jupyter:
%load_ext wurlitzer
jfs 的答案给了我一个错误,所以我想出了另一个基于这个答案的解决方案。
ValueError: I/O operation on closed file.
import contextlib
@contextlib.contextmanager
def silence_stderr():
stderr_fd = sys.stderr.fileno()
orig_fd = os.dup(stderr_fd)
null_fd = os.open(os.devnull, os.O_WRONLY)
os.dup2(null_fd, stderr_fd)
try:
yield
finally:
os.dup2(orig_fd, stderr_fd)
os.close(orig_fd)
os.close(null_fd)
正如预期的那样,用法非常简单。
with silence_stderr():
# call python module: stderr will be silenced
# call c/c++ library: stderr will be silenced
您可以通过简单的查找替换轻松修改代码以静音stdout
而不是stderr
。
难道您不能像在 Python 中那样做到这一点吗? 你会导入 sys 并将 sys.stdout 和 sys.stderr 指向不是默认的 sys.stdout 和 sys.stderr 的东西吗? 我一直在一些应用程序中这样做,我必须从库中获取输出。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.