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

For the ALSA errors in particular, you can use ALSA's snd_lib_error_set_handler function as described for example in this question.

For one of my projects, I created a module called mute_alsa, which looks like this:

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

And you use it simply by putting the following into your code:

import mute_alsa

In a more general sense, you can prevent anything from printing to stderr by simply closing the associated file descriptor. For example, if we try to rm a file that doesn't exist, we get an error message printed to stderr:

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

If we close the stderr file descriptor:

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

We no longer see any error messages:

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

The downside to this approach is that it silences all messages to stderr, which means you will no longer see legitimate error messages. This may lead to surprising failure modes (the program exited without printing any errors!).


You could switch from PyAudio to the sounddevice module, which already takes care of silencing the terminal output (see #12). This is how it is done there (using 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)

If you want a pure Python solution, you can try this context manager:

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)

This is a very helpful blog post about the topic: http://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/.


UPDATE:

The context manager above silences the standard error output (stderr), which is used for the annoying messages from PortAudio mentioned in the original question. To get rid of the standard output (stdout), as in your updated question, you'll have to replace sys.stderr with sys.stdout and the file descriptor 2 with the number 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)