Как вообще можно заглушить вывод терминала исполняемых файлов, запускаемых функциями Python?

Я хочу подавить весь вывод терминала, созданный функцией, которая запускает исполняемые файлы.

Я попытался подавить вывод функции 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()

Мне не удалось подавить вывод терминала из print или ls, и я не уверен, почему.


person d3pd    schedule 30.04.2016    source источник


Ответы (2)


Вы можете переключиться с PyAudio на модуль sounddevice, который уже позаботится об отключении вывода терминала (см. #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)

Это очень полезная запись в блоге на эту тему: 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)
person Matthias    schedule 01.05.2016
comment
Большое спасибо за подробное решение. Я изучу модуль звукового устройства, как было предложено, и рассмотрю его решение для отключения звука PortAudio. Что касается чистого общего подхода к полному отключению терминала (будь то для вывода Python или исполняемых файлов, запускаемых Python), который вы предложили, мне не удалось заставить его подавить вывод терминала. Я поместил свой код в редактирование вопроса. Есть ли у вас какие-либо идеи о том, как изменить его, чтобы вывод терминала print и ls подавлялся? - person d3pd; 03.05.2016
comment
Ах, вот оно. Возникла необходимость обрабатывать stdout. Еще раз спасибо за четкое и подробное решение. - person d3pd; 04.05.2016

В частности, для ошибок ALSA вы можете использовать функцию snd_lib_error_set_handler ALSA, как описано, например, в этом вопросе.

Для одного из моих проектов я создал модуль под названием 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, что означает, что вы больше не будете видеть законные сообщения об ошибках. Это может привести к неожиданным сбоям (программа завершилась без вывода ошибок!).

person larsks    schedule 30.04.2016
comment
Эй, большое спасибо за ваше подробное решение (и за превентивное предложение отключения звука ALSA). Когда я помещаю os.close(sys.stdout.fileno()) и os.close(sys.stderr.fileno()) в функции ввода или инициализации контекстного менеджера выше, вывод терминала, похоже, не подавляется. Вы видите, что мне не хватает? - person d3pd; 01.05.2016
comment
Есть ли способ получить часть varg args в обработчике python? Обработчик получает строку «fmt», но не параметры, отправленные исходному обработчику ошибок ALSA. Я не хочу заглушать ошибки, просто регистрируйте их один раз для каждого случая. - person Zocket; 12.09.2018