【问题标题】:Python: equivalent of input using sys.stdinPython:相当于使用 sys.stdin 的输入
【发布时间】:2012-11-05 19:00:34
【问题描述】:

我想测试一些直接使用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,所以这对我来说更容易。
  • 具体用例是什么?
  • @JonClements:它只是一个简单的前端,要求用户指定一些选项。考虑一下,将答案列表作为可选参数接受可能更容易,但我仍然想知道是否有一种简单的方法可以复制 input 的行为。

标签: python dependency-injection python-3.x mocking stdin


【解决方案1】:

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_GetLinecalls the 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"

【讨论】:

    【解决方案2】:

    如果你为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 中带有 defined meaning 的技术术语。为了让input() 正常工作,您可能需要一个真实的文件对象,由真实的操作系统文件支持。我没想到,例如io.StringIO 在这种情况下工作。
    • 我刚刚用io.StringIO 尝试过,它确实有效。我将使用适用于 io.StringIO 的代码更新我的答案。
    • 我没有尝试。我的猜测是基于这样一个事实,根据 OP,input() 的实现使用了 fileno 属性,这仅对 OS 文件有意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-23
    • 2017-05-28
    • 2017-05-13
    • 1970-01-01
    • 2016-12-09
    • 2014-03-14
    • 1970-01-01
    相关资源
    最近更新 更多