Python 函数的可执行文件 运行 的终端输出如何以一般方式静音?

How can the terminal output of executables run by Python functions be silenced in a general way?

我想抑制 运行 可执行文件的函数产生的所有终端输出。

我试图通过使用上下文管理器来抑制 Python 函数的输出,该上下文管理器在每次调用该函数时临时重新定义 stdout 和 stderr。这抑制了函数中 print 调用产生的终端输出,但当函数调用产生终端输出的可执行文件时它似乎不起作用。

那么,如何抑制 Python 函数调用的可执行文件的输出?

我的代码如下。我已经包含了一个调用 ls 的示例函数来尝试说明我想要抑制的终端输出类型(尽管我正在处理的函数是不同的)。

#!/usr/bin/env python

import os
import subprocess
import sys

def main():

    print("hello")

    with silence():
        print("there")

    print("world")

    with silence():
        engage_command(command = "ls")

class silence(object):

    def __init__(
        self,
        stdout = None,
        stderr = None
        ):
        if stdout == None and stderr == None:
            devnull = open(os.devnull, "w")
            stdout = devnull
            stderr = devnull
        self._stdout = stdout or sys.stdout
        self._stderr = stderr or sys.stderr

    def __enter__(
        self
        ):
        self.old_stdout = sys.stdout
        self.old_stderr = sys.stderr
        self.old_stdout.flush()
        self.old_stderr.flush()
        sys.stdout = self._stdout
        sys.stderr = self._stderr

    def __exit__(
        self,
        exc_type,
        exc_value,
        traceback
        ):
        self._stdout.flush()
        self._stderr.flush()
        sys.stdout = self.old_stdout
        sys.stderr = self.old_stderr

def engage_command(
    command = None
    ):
    process = subprocess.Popen(
        [command],
        shell      = True,
        executable = "/bin/bash")
    process.wait()
    output, errors = process.communicate()
    return output

if __name__ == "__main__":
    main()

在我的特殊情况下,我正在尝试 运行 以下函数(而不是上面的 ls 函数):

with propyte.silence():
    stream = pyaudio.PyAudio().open(
        format   = pyaudio.PyAudio().get_format_from_width(1),
        channels = 1, 
        rate     = bitrate, 
        output   = True
    )

当 运行 时,会产生如下输出:

ALSA lib pcm_dsnoop.c:606:(snd_pcm_dsnoop_open) unable to open slave
ALSA lib pcm_dmix.c:1029:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2266:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_dmix.c:1029:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for 4294967295, skipping unlock

我想抑制那个输出。


编辑:测试 由@Matthias

提供
#!/usr/bin/env python

import contextlib
import os
import subprocess
import sys

def main():

    print("hello")

    with silence():
        print("there")

    print("world")

    with silence():
        engage_command(command = "ls")

@contextlib.contextmanager
def silence():
    devnull = os.open(os.devnull, os.O_WRONLY)
    old_stderr = os.dup(2)
    sys.stderr.flush()
    os.dup2(devnull, 2)
    os.close(devnull)
    try:
        yield
    finally:
        os.dup2(old_stderr, 2)
        os.close(old_stderr)

def engage_command(
    command = None
    ):
    process = subprocess.Popen(
        [command],
        shell      = True,
        executable = "/bin/bash")
    process.wait()
    output, errors = process.communicate()
    return output

if __name__ == "__main__":
    main()

我没有成功抑制 printls 的终端输出,我不确定为什么。

特别是对于 ALSA 错误,您可以使用 ALSA 的 snd_lib_error_set_handler 函数,例如 in this question.

对于我的一个项目,我创建了一个名为 mute_alsa 的模块,它看起来像这样:

import ctypes

ERROR_HANDLER_FUNC = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int,
                                      ctypes.c_char_p, ctypes.c_int,
                                      ctypes.c_char_p)


def py_error_handler(filename, line, function, err, fmt):
    pass

c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)

try:
    asound = ctypes.cdll.LoadLibrary('libasound.so.2')
    asound.snd_lib_error_set_handler(c_error_handler)
except OSError:
    pass

只需将以下代码放入代码中即可使用它:

import mute_alsa

从更一般的意义上讲,您可以阻止 任何东西 打印到 stderr 只需关闭关联的文件描述符即可。为了 例如,如果我们尝试 rm 一个不存在的文件,我们会得到一个错误 消息打印到 stderr:

>>> import sys
>>> import os
>>> os.system('rm /doesnotexist')
rm: cannot remove ‘/doesnotexist’: Is a directory
256

如果我们关闭 stderr 文件描述符:

>>> os.close(sys.stderr.fileno())

我们不再看到任何错误消息:

>>> os.system('rm /doesnotexist')

这种方法的缺点是它会使 所有 消息静音 stderr,这意味着您将不再看到合法错误 消息。这可能会导致令人惊讶的故障模式(程序 没有打印任何错误就退出了!)。

您可以从 PyAudio 切换到 sounddevice module, which already takes care of silencing the terminal output (see #12)。这是在那里完成的(使用 CFFI):

from cffi import FFI
import os

ffi = FFI()
ffi.cdef("""
/* from stdio.h */
FILE* fopen(const char* path, const char* mode);
int fclose(FILE* fp);
FILE* stderr;  /* GNU C library */
FILE* __stderrp;  /* Mac OS X */
""")
try:
    stdio = ffi.dlopen(None)
    devnull = stdio.fopen(os.devnull.encode(), b'w')
except OSError:
    return
try:
    stdio.stderr = devnull
except KeyError:
    try:
        stdio.__stderrp = devnull
    except KeyError:
        stdio.fclose(devnull)

如果你想要一个纯粹的 Python 解决方案,你可以试试这个上下文管理器:

import contextlib
import os
import sys

@contextlib.contextmanager
def ignore_stderr():
    devnull = os.open(os.devnull, os.O_WRONLY)
    old_stderr = os.dup(2)
    sys.stderr.flush()
    os.dup2(devnull, 2)
    os.close(devnull)
    try:
        yield
    finally:
        os.dup2(old_stderr, 2)
        os.close(old_stderr)

这是一个非常有用的博客post,主题是:http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/


更新:

上面的上下文管理器使标准错误输出静音(stderr),它用于原始问题中提到的来自 PortAudio 的恼人消息。要摆脱标准输出(stdout),如更新后的问题,您必须将 sys.stderr 替换为 sys.stdout 并将文件描述符 2 替换为数字1:

@contextlib.contextmanager
def ignore_stdout():
    devnull = os.open(os.devnull, os.O_WRONLY)
    old_stdout = os.dup(1)
    sys.stdout.flush()
    os.dup2(devnull, 1)
    os.close(devnull)
    try:
        yield
    finally:
        os.dup2(old_stdout, 1)
        os.close(old_stdout)

下午好,为了解决错误,只需添加以下解释:

导入声音设备

如果我需要安装:

python3 -m pip install sounddevice