Как предотвратить совместную печать библиотеки C на stdout в python? - PullRequest
40 голосов
/ 22 февраля 2011

Я работаю с библиотекой Python, которая импортирует разделяемую библиотеку 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\n")

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

Этот первый подход наполовину работает:
- По какой-то причине перед выводом stdout требуется оператор "print", в противном случае всегда выводится приветственное слово. В результате он напечатает пустую строку вместо всех фазз, которые обычно выводит библиотека.
- Больше раздражает то, что происходит сбой при перенаправлении в файл:

$python test.py > foo && cat foo

hello world

Моя вторая попытка питона была вдохновлена ​​другой подобной темой, приведенной в комментариях:

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)

Этот файл также не может предотвратить печать "привет".

Поскольку я чувствовал, что это немного низкий уровень, я решил полностью перейти на 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\n"); // 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\n")

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

Это печатает "привет", даже если я libc.fflush (stdout) сразу после printf. Я начинаю думать, что в Python невозможно сделать то, что я хочу. Или, может быть, способ, которым я получаю указатель файла на стандартный вывод, неправильный.

Что вы думаете?

Ответы [ 5 ]

21 голосов
/ 30 июля 2013

На основании @ ответа Инона Эрлиха . Этот вариант пытается избежать утечки файловых дескрипторов:

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
15 голосов
/ 24 февраля 2011

Да, вы действительно хотите использовать os.dup2 вместо os.dup, как ваша вторая идея.Ваш код выглядит несколько окольными.Не копайтесь с /dev записями, кроме /dev/null, это не нужно.Здесь также нет необходимости писать что-либо на языке C.

Хитрость заключается в том, чтобы сохранить stdout fdes с помощью dup, а затем передать его в fdopen, чтобы создать новый sys.stdout объект Python.Тем временем, откройте fdes для /dev/null и используйте dup2, чтобы перезаписать существующие stdout fdes.Затем закройте старые поля до /dev/null.Вызов dup2 необходим, потому что мы не можем сказать open, какие значения мы хотим, чтобы он возвратил, dup2 действительно единственный способ сделать это.

Редактировать: А если вы перенаправляете в файл, то stdout не буферизуется, поэтому вы должны очистить его.Вы можете сделать это из Python, и он будет корректно взаимодействовать с Си.Конечно, если вы вызываете эту функцию до того, как напишите что-либо в 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...
11 голосов
/ 10 февраля 2013

Объединение обоих ответов - https://stackoverflow.com/a/5103455/1820106 & https://stackoverflow.com/a/4178672/1820106 для диспетчера контекста, который блокирует печать на стандартный вывод только для своей области (код в первом ответе заблокировал любой внешний вывод, последний ответ пропустил 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)
4 голосов
/ 24 февраля 2011

Вот как я наконец сделал. Я надеюсь, что это может быть полезно для других людей (это работает на моей станции 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\n")

shutup.stdout_on()
0 голосов
/ 23 февраля 2011

Не могли бы вы сделать это так же, как в Python?Вы импортировали бы sys и указали бы sys.stdout и sys.stderr на что-то, что не является sys.stdout и sys.stderr по умолчанию?Я делаю это все время в нескольких приложениях, где мне приходится отбрасывать вывод из библиотеки.

...