使用sys.stdin接收多行输入

5
我有以下函数:
def getInput():
    # define buffer (list of lines)
    buffer = []
    run = True
    while run:
        # loop through each line of user input, adding it to buffer
        for line in sys.stdin.readlines():
            if line == 'quit\n':
                run = False
            else:
                buffer.append(line.replace('\n',''))
    # return list of lines
    return buffer

这个函数叫做takeCommands(),在我的程序中被调用以实际运行程序。

然而,它什么也没做。我希望将每一行添加到一个数组中,一旦一行等于“quit”,就停止接受用户输入。我尝试过使用 for line in sys.stdin.readlines()for line sys.stdin,但它们都没有注册我的任何输入(我在Windows命令提示符中运行它)。有什么想法吗?谢谢。


1
问题是'quit'!= 'quit\n' - JBernardo
1
请注意每个“line”末尾都有一个“\n”,因此您永远不会触发“run = False”的条件。 - mgilson
我已经修改了我的原始帖子,尝试了下一步操作。仍然无法注册任何输入或停止等待输入。 - Jakemmarsh
你的代码(目前发布的)存在问题,就是你调用了sys.stdin.readlines()。这会返回一个行的列表。显然,在获取所有行之前,它无法生成该列表,这意味着在关闭标准输入之前它无法返回。解决方法就是……不要调用readlines()。当然,解决了JBernardo展示的问题后,可能会出现新的问题,但这并不意味着你不需要解决这个问题。 - abarnert
4个回答

6
所以,我把你的代码从函数中拿出来并进行了一些测试。
import sys
buffer = []
while run:
    line = sys.stdin.readline().rstrip('\n')
    if line == 'quit':
        run = False
    else:
        buffer.append(line)

print buffer

改动:

  • 删除了‘for’循环。
  • 使用'readline'代替'readlines'。
  • 去掉输入后面的'\n',这样之后的处理会更加容易。

另一种方式:

import sys
buffer = []
while True:
    line = sys.stdin.readline().rstrip('\n')
    if line == 'quit':
        break
    else:
        buffer.append(line)
print buffer

将“run”变量删除,因为它并不是真正需要的。

3
我会使用itertools.takewhile来完成这个任务:
import sys
import itertools
print list(itertools.takewhile(lambda x: x.strip() != 'quit', sys.stdin))

另一种方法是使用两个参数的 iter 表单:

print list(iter(raw_input,'quit'))

这样做的好处是raw_input会处理所有的行缓冲问题,它会自动为您剥离换行符 - 但是如果用户忘记向脚本中添加quit,则它将无限循环直到内存耗尽。

这两种方法都能通过测试:

python test.py <<EOF
foo
bar
baz
quit
cat
dog
cow
EOF

使用iter的两个参数形式通常比takewhile更简单。 - JBernardo
1
@JBernardo-- 它们在很大程度上是等价的(如果我没记错的话),但我发现 takewhile 更容易理解,因为它只做一件事情。使用 iter,读者需要认识到你在使用两个参数的形式,然后他们需要记住这与一个参数的形式有什么不同等等。当然,你需要多打几个字 - 还要导入 itertools,但我觉得这是为了额外明确而付出的小代价。 - mgilson
不幸的是,这是一项作业任务,并且他们特别要求使用标准输入和输出以使他们的大规模测试更容易。 - Jakemmarsh
@Jakemmarsh -- 你可能遇到了行缓冲问题 - mgilson
@JBernardo -- 还添加了一个iter的例子。 - mgilson
@Jakemmarsh:raw_input 从标准输入读取。就此而言,takewhile 版本显然以与您的程序相同的方式使用 stdin(除了它不调用 readlines(),这是您代码的问题)。那么...您认为这违反了教授要求使用标准输入的要求吗? - abarnert

0

以下代码(至少在Linux上)是有效的。

import pathlib

pathlib.Path("/proc/self/fd/0").read_text()

以上,/proc/self/fd/0 表示“标准输入(stdin)”。
按下 Ctrl + D 结束多行输入。

0

这段代码存在多个独立的问题:

while run:
    # loop through each line of user input, adding it to buffer
    for line in sys.stdin.readlines():
        if line == 'quit':
            run = False

首先,您有一个内部循环,直到所有行都被处理完才结束,即使在某些时候键入“quit”。设置run = False不能打破该循环。而不是在键入“quit”后立即退出,它会继续进行,直到查看了所有行,然后如果您在任何时候键入“quit”,则退出。

您可以通过在run = False之后添加break来轻松解决此问题。


但是,无论是否修复了这个问题,如果您在外部循环的第一次运行中没有输入“退出”,由于您已经读取了所有输入,因此没有其他内容可读取,因此您将永远运行一个空的内部循环,而您永远无法退出。

您有一个循环,意味着“读取并处理所有输入”。您希望执行一次。那么,外部循环应该是什么?它不应该是任何方式;做一次事情的方法是不使用循环。因此,要修复此问题,请摆脱runwhile run:循环;只需使用内部循环即可。


然后,如果您键入 "quit",line 实际上将是 "quit\n",因为 readlines 没有去除换行符。

您可以通过测试 "quit\n" 或对行进行 strip 来解决这个问题。


最后,即使您解决了所有这些问题,您仍然需要永远等待才能执行任何操作。readlines返回一个行的list。它唯一可能做到这一点的方式是通过读取将在stdin上出现的所有行。在读取所有这些行之前,甚至无法开始循环。

当标准输入是文件时,当文件结束时会发生这种情况,因此不是太糟糕。但是当标准输入是Windows命令提示符时,命令提示符永远不会结束。* 因此,这需要很长时间。您无法开始处理行列表,因为等待行列表需要很长时间。

解决方案是不使用readlines()。实际上,从任何地方调用readlines()都没有好的理由,无论是stdin还是其他什么。任何readlines可以处理的内容都已经是一个包含行的可迭代对象,就像readlines会给你的list一样,只不过它是“懒惰”的:它可以一次给你一行,而不是等待并一次性给你所有行。(即使你真的需要列表,也只需执行list(f)而不是f.readlines()。)
因此,不要使用for line in sys.stdin.readlines():,而是使用for line in sys.stdin:(或者更好的是,完全替换显式循环并使用迭代器转换序列,如mgilson的答案所示。)
JBernardo、Wing Tang Wong等提出的修复方法都是正确且必要的。之所以他们的方法没有解决您的问题,是因为如果您有4个错误,并修复了一个,您的代码仍然不起作用。这正是为什么“不起作用”在编程中并不是一个有用的衡量标准,您必须调试实际出错的地方,才能知道自己是否在取得进展。

* 我在关于 stdin 永远不会结束的说法上撒了个小谎。如果您键入控制-Z(您可能需要跟随一个回车),那么 stdin 就会结束。但是,如果您的任务是使其在用户键入“quit”时立即退出,那么只有在用户键入“quit”,然后返回、控制-Z、再次返回的情况下,才能被视为成功。


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