¿Cómo puedo evitar que una biblioteca compartida de C se imprima en la salida estándar en python?

Trabajo con una biblioteca de Python que importa una biblioteca compartida de C que se imprime en stdout. Quiero una salida limpia para usarla con tuberías o para redirigir en archivos. Las impresiones se realizan fuera de python, en la biblioteca compartida.

Al principio, mi enfoque era:

# 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) 

Este primer acercamiento es medio trabajo:
– Por alguna razón, necesita una statement de “impresión” justo antes de mover la salida estándar, de lo contrario, siempre se imprime hola palabra. Como resultado, imprimirá una línea vacía en lugar de todos los errores que la biblioteca suele generar.
– Más molesto, falla al redirigir a un archivo:

 $python test.py > foo && cat foo hello world 

Mi segundo bash con Python se inspiró en otro hilo similar dado en los comentarios:

 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) 

Este también falla para evitar que “hola” se imprima.

Ya que sentí que este era un nivel un poco bajo, entonces decidí ir completamente con los tipos. Me inspiré en este progtwig de C, que no imprime nada:

 #include  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; } 

Construí el siguiente ejemplo:

 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) 

Esto imprime “hola”, incluso si libc.fflush (stdout) justo después de la impresión. Estoy empezando a pensar que puede que no sea posible hacer lo que quiero en Python. O tal vez la forma en que obtengo un apuntador de archivo a la salida estándar no es la correcta.

¿Qué piensas?

Basado en la respuesta de @Yinon Ehrlich . Esta variante intenta evitar la pérdida de descriptores de archivos:

 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) # restre stdout. # buffering and flags such as # CLOEXEC may be different 

Sí, realmente quieres usar os.dup2 lugar de os.dup , como tu segunda idea. Su código se ve algo redondeado. No juegues con las entradas /dev excepto para /dev/null , no es necesario. También es innecesario escribir nada en C aquí.

El truco es guardar los archivos stdout fdes usando dup , luego pasarlo a fdopen para hacer que el nuevo sys.stdout Python sea un objeto. Mientras tanto, abra un archivo fdes en /dev/null y use dup2 para sobrescribir los archivos stdout existentes. A continuación, cierre las fdes antiguas a /dev/null . La llamada a dup2 es necesaria porque no podemos decir qué fdes queremos que devuelva, dup2 es realmente la única forma de hacerlo.

Edición: y si está redirigiendo a un archivo, entonces la salida estándar no tiene búfer de línea, por lo que debe vaciarlo. Puedes hacerlo desde Python e interactuará con C correctamente. Por supuesto, si llama a esta función antes de escribir algo en la stdout , entonces no importa.

Aquí hay un ejemplo que acabo de probar que funciona en mi sistema.

 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..." 

El módulo "zook" es una biblioteca muy simple en C.

 #include  #include  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); } 

¿Y la salida?

 $ python2.5 test.py myfunc called Redirecting stdout But python can still print to stdout... 

¿Y redirigir a archivos?

 $ python2.5 test.py > test.txt $ cat test.txt myfunc called Redirecting stdout But python can still print to stdout... 

Combinación de ambas respuestas: https://stackoverflow.com/a/5103455/1820106 & https://stackoverflow.com/a/4178672/1820106 para el administrador de contexto que bloquea la impresión en la salida estándar solo para su scope (el código en la primera respuesta Bloqueado cualquier salida externa, la última respuesta perdió el sys.stdout.flush () al final):

 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) 

Así es como finalmente lo hice. Espero que esto pueda ser útil para otras personas (esto funciona en mi estación de Linux).

Presento con orgullo el libshutup, diseñado para hacer que las bibliotecas externas se callen.

1) Copia el siguiente archivo

 // file: shutup.c #include  #include  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) Comstackrlo como una biblioteca compartida

 gcc -Wall -shared shutup.c -fPIC -o libshutup.so 

3) Úsalo en tu código como este

 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() 

¿No podrías hacer esto de la misma manera que lo harías en Python? ¿Importaría sys y apuntaría sys.stdout y sys.stderr a algo que no es sys.stdout y sys.stderr predeterminados? Hago esto todo el tiempo en algunas aplicaciones donde tengo que absorber la salida de una biblioteca.