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
тази тема може да е полезна 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 функцията input на Python ще го използва вместо стандартния вход. Но не забравяйте да преназначите 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
Файлоподобен обект е технически термин с дефинирано значение в Python. За да работи това правилно с input(), вероятно ще ви е необходим реален файлов обект, подкрепен от истински OS файл. Не бих очаквал, че напр. io.StringIO работи в този контекст. - person Sven Marnach; 05.11.2012
comment
Току-що го пробвах с io.StringIO и наистина работи. Ще актуализирам отговора си с кода, който работи с io.StringIO. - person mazayus; 05.11.2012
comment
не пробвах. Предположението ми се основаваше на факта, че според OP изпълнението на input() използва атрибута fileno, което има смисъл само за OS файлове. - person Sven Marnach; 05.11.2012