Python:使用sys.stdin的等效输入方法

5

我想测试一些直接使用printinput函数的Python 3代码。据我所知,最简单的方法是通过依赖注入来实现:修改代码,使其将输入和输出流作为参数传递,并默认使用sys.stdinsys.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.encodingsys.stdin.errors,而不是调用任何read*方法——我不知道从哪里开始模拟这些。


此外,它还可以使用GNU readline库。实际上,我从来不使用input,这让我更容易一些。 - Keith
具体使用场景是什么? - Jon Clements
@JonClements:这只是一个简单的前端,要求用户指定一些选项。想想看,也许更容易的方法是接受一个答案列表作为可选参数,但我仍然很想知道是否有一种简单的方法来复制“input”的行为。 - James
这个线程可能会有用 https://dev59.com/SWrWa4cB1Zd3GeqP8j70#13143479 - Joran Beasley
2个回答

7

如果你将类文件对象赋值给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.txtoutputfile.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)

“类文件对象”是Python中具有定义意义的技术术语。为了使其与input()正常工作,您可能需要一个真正的文件对象,由真正的操作系统文件支持。我不认为例如io.StringIO在这种情况下能够正常工作。 - Sven Marnach
1
我刚刚尝试了使用io.StringIO,它确实有效。我将使用适用于io.StringIO的代码更新我的答案。 - mazayus
我没有尝试过。我的猜测是基于OP所说的input()实现使用fileno属性,这只对操作系统文件有意义。 - Sven Marnach

7

input() 只有在 stdinstdout 未被更改时才能执行你提到的魔法,因为只有这样它才能使用像 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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接