Python: эквивалент ввода с использованием sys.stdin

Я хочу протестировать некоторый код (python 3), который напрямую использует функции print и input. Насколько я понимаю, проще всего это сделать с помощью внедрения зависимостей: изменить код так, чтобы он принимал входные и выходные потоки в качестве аргументов, используя по умолчанию sys.stdin и sys.stdout и передавая фиктивные объекты во время тестирования. Очевидно, что делать с print вызовами:

print(text)
#replaced with...
print(text, file=output_stream)

Однако input не имеет аргументов для входных и выходных потоков. Правильно ли следующий код воспроизводит свое поведение?

text = input(prompt)
#replaced with...
print(prompt, file=output_stream, end='')
text = input_stream.readline()[:-1]

Я взглянул на реализацию input, и она делает довольно много волшебства, вызывая sys.stdin.fileno и исследуя sys.stdin.encoding и sys.stdin.errors, а не вызывая какой-либо из методов read* - я не знаю, с чего начать, издеваясь над ними.


person James    schedule 05.11.2012    source источник
comment
кроме того, он может также использовать библиотеку GNU readline. На самом деле я никогда не использую input, так что мне проще.   -  person Keith    schedule 05.11.2012
comment
Каков конкретный вариант использования?   -  person Jon Clements♦    schedule 05.11.2012
comment
@JonClements: это просто интерфейс, который просит пользователя указать несколько параметров. Размышляя об этом, может быть проще просто принять список ответов в качестве необязательного аргумента, но мне все равно было бы интересно узнать, есть ли простой способ дублирования поведения input.   -  person James    schedule 05.11.2012
comment
этот поток может быть полезен я избегаю обработки пустого стандартного ввода с помощью python"> stackoverflow.com/questions/13143218/   -  person Joran Beasley    schedule 05.11.2012


Ответы (2)


input() делает то волшебство, о котором вы упомянули, только тогда, когда stdin и stdout не изменены, потому что только тогда он может использовать такие вещи, как библиотека readline. Если вы замените их чем-то другим (реальными файлами или нет), это сводится к этому коду:

/* Fallback if we're not interactive */
if (promptarg != NULL) {
    if (PyFile_WriteObject(promptarg, fout, Py_PRINT_RAW) != 0)
         return NULL;
}
tmp = _PyObject_CallMethodId(fout, &PyId_flush, "");
if (tmp == NULL)
    PyErr_Clear();
else
    Py_DECREF(tmp);
return PyFile_GetLine(fin, -1);

Где PyFile_GetLine вызывает метод readline. Таким образом, издевательство над sys.std* будет работать.

Рекомендуется делать это с try: finally:, процессором контекста или модулем mock, чтобы выходные данные восстанавливались, даже если тестируемый код дает сбой с исключениями:

from unittest.mock import patch
from io import StringIO

with patch("sys.stdin", StringIO("FOO")), patch("sys.stdout", new_callable=StringIO) as mocked_out:
    x = input()
    print("Read:", x)

assert mocked_out.getvalue() == "Read: FOO\n"
person lqc    schedule 05.11.2012

Если вы назначите файлоподобный объект sys.stdin, функция Python input будет использовать его вместо стандартного ввода. Но не забудьте переназначить sys.stdin обратно на стандартный ввод после того, как вы закончите с ним. Тот же трюк применим к sys.stdout. Вы можете сделать что-то вроде этого:

original_stdin = sys.stdin
sys.stdin = open('inputfile.txt', 'r')

original_stdout = sys.stdout
sys.stdout = open('outputfile.txt', 'w')

response = input('say hi: ')
print(response)

sys.stdin = original_stdin
sys.stdout = original_stdout

Эти две линии

response = input('say hi: ')
print(response)

будут использовать указанные файлы (inputfile.txt и outputfile.txt) вместо стандартного ввода и стандартного вывода.

ОБНОВЛЕНИЕ: Если вы не хотите иметь дело с физическими файлами, взгляните на модуль io. Он предоставляет класс io.StringIO, который позволяет выполнять операции с текстовым потоком в памяти.

original_stdin = sys.stdin
sys.stdin = io.StringIO('input string')

original_stdout = sys.stdout
sys.stdout = io.StringIO()

response = input('say hi: ')
print(response)

output = sys.stdout.getvalue()

sys.stdin = original_stdin
sys.stdout = original_stdout

print(output)
person mazayus    schedule 05.11.2012
comment
Файлоподобный объект — это технический термин с определенным значением в Питоне. Чтобы это правильно работало с input(), вам, вероятно, понадобится реальный файловый объект, поддерживаемый реальным файлом ОС. Я бы не ожидал, что, например. io.StringIO работает в этом контексте. - person Sven Marnach; 05.11.2012
comment
Я только что попробовал это с io.StringIO, и это действительно работает. Я обновлю свой ответ кодом, который работает с io.StringIO. - person mazayus; 05.11.2012
comment
я не пробовал. Мое предположение было основано на том факте, что согласно ОП реализация input() использует атрибут fileno, что имеет смысл только для файлов ОС. - person Sven Marnach; 05.11.2012