如何防止 C 共享库在 python 中的 stdout 上打印?

2025-03-06 08:55:00
admin
原创
68
摘要:问题描述:我使用一个 python 库,它导入了一个在 stdout 上打印的 C 共享库。我想要一个干净的输出,以便将其与管道一起使用或在文件中重定向。打印是在 python 之外的共享库中完成的。一开始我的做法是:# file: test.py import os from ctypes import *...

问题描述:

我使用一个 python 库,它导入了一个在 stdout 上打印的 C 共享库。我想要一个干净的输出,以便将其与管道一起使用或在文件中重定向。打印是在 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
")

os.close(1)
os.dup(savestdout)
os.close(savestdout)

第一种方法只成功了一半:

  • 出于某种原因,在移动 stdout 之前需要一个“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
")

os.dup2(oldstdout, 1)

这个也无法阻止打印“hello”。

由于我觉得这有点低级,所以我决定完全使用 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
"); // 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
")

libc.freopen("/dev/fd/" + str(saved_stdout), "w", stdout)

即使我在 printf 之后立即使用 libc.fflush(stdout),此命令也会打印“hello”。我开始认为在 python 中可能无法实现我想要的功能。或者我获取指向 stdout 的文件指针的方式不正确。

你怎么认为?


解决方案 1:

根据@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

解决方案 2:

结合两个答案 - https://stackoverflow.com/a/5103455/1820106https://stackoverflow.com/a/4178672/1820106到上下文管理器,仅在其范围内阻止打印到 stdout(第一个答案中的代码阻止了任何外部输出,后一个答案在最后错过了 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)

解决方案 3:

是的,您确实想使用os.dup2而不是os.dup,就像您的第二个想法一样。您的代码看起来有些迂回。不要乱用/dev以外的条目/dev/null,这是不必要的。在这里也没有必要用 C 写任何东西。

诀窍是stdout使用 保存 fdes dup,然后将其传递给fdopen以创建新的sys.stdoutPython 对象。同时,打开一个 fdes 到/dev/null并使用dup2覆盖现有的stdoutfdes。然后关闭旧的 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...

解决方案 4:

这里的最佳答案非常好。但是,sys.stdout.close()如果使用 Python 笔记本,它需要与 Juypter 冲突。有一个名为 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

解决方案 5:

这是我最终的做法。希望这对其他人有用(这在我的 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
")

shutup.stdout_on()

解决方案 6:

之前的所有答案都对我不起作用,要么导致分段错误,要么无法正确关闭输出。复制此答案并添加额外的 close 语句似乎没有泄漏任何文件描述符,并且在我的案例中有效:

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)
        os.close(self._oldstdout_fno) # Additional close to not leak fd

99%从此处复制并关闭文件,如该答案中所述。

解决方案 7:

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通过简单的查找替换。

解决方案 8:

难道你不能像在 Python 中那样做吗?你会导入 sys 并将 sys.stdout 和 sys.stderr 指向非默认 sys.stdout 和 sys.stderr 的东西?我在一些需要从库中获取输出的应用程序中一直这样做。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2560  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1552  
  华为IPD研发项目管理在企业的创新与发展中扮演着至关重要的角色,而高效复盘则是确保项目不断优化、持续提升的关键环节。通过合理运用一系列工具,华为能够深入剖析项目过程中的得失,为未来项目提供宝贵经验。以下将详细解析助力华为IPD研发项目管理实现高效复盘的5大工具。项目数据仪表盘项目数据仪表盘是一种直观呈现项目关键数据的工...
华为IPD产品开发流程   0  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Hive、Monday、GanttPRO、QuickBase、Planview、Productive、Plutio、Forecast、Runn。在当今快速变化的商业环境中,项目管理软件已成为企业提升效率、优化资源分配和确保项目成功的关键工具。然而,面对市场上琳琅满...
项目管理软件   1  
  PLM(产品生命周期管理)项目管理对于企业产品的全生命周期管控至关重要,而其中资源分配的优化以及高效团队协作机制的构建更是决定项目成败的关键因素。合理的资源分配能确保项目在预算和时间范围内顺利推进,高效的团队协作机制则能充分发挥团队成员的优势,提升整体项目的执行效率。接下来,我们将深入探讨如何通过四个关键步骤来优化资源...
国产plm软件排名   2  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用