получение многострочного ввода с помощью sys.stdin

У меня есть следующая функция:

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(), которая вызывается для фактического запуска моей программы.

Однако это ничего не делает. Я надеюсь добавить каждую строку в массив, и как только строка == 'выйти', она перестанет принимать пользовательский ввод. Я пробовал как for line in sys.stdin.readlines(), так и for line sys.stdin, но ни один из них не регистрирует мой ввод (я запускаю его в командной строке Windows). Любые идеи? Спасибо


person Jakemmarsh    schedule 28.03.2013    source источник
comment
проблема 'quit' != 'quit\n'   -  person JBernardo    schedule 29.03.2013
comment
Обратите внимание, что каждый line будет иметь '\n' в конце, так что вы никогда не столкнетесь с условием run = False.   -  person mgilson    schedule 29.03.2013
comment
Я изменил свой исходный пост на то, что я пробовал дальше. По-прежнему не регистрирует никаких входных данных и никогда не выходит из ожидания ввода.   -  person Jakemmarsh    schedule 29.03.2013
comment
Проблема с вашим кодом (как опубликовано в настоящее время) заключается в том, что вы вызываете sys.stdin.readlines(). Это возвращает list строк. Очевидно, что он не может сделать это list, пока не соберет все строки, что означает, что он не может вернуться, пока вы не закроете стандартный ввод. Решение этой проблемы состоит в том, чтобы просто… не звонить readlines(). Вполне возможно, что у вас возникла новая проблема после решения этой, точно так же, как у вас возникла новая проблема после исправления той, которую показал JBernardo, но это не значит, что вам не нужно решать эту проблему. исправить.   -  person abarnert    schedule 29.03.2013


Ответы (3)


Итак, выньте свой код из функции и запустите несколько тестов.

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, так как она на самом деле не нужна.

person Wing Tang Wong    schedule 28.03.2013

Я бы использовал 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
person mgilson    schedule 28.03.2013
comment
использование двухаргументной формы iter обычно проще, чем takewhile - person JBernardo; 29.03.2013
comment
@JBernardo - они в значительной степени эквивалентны (если я правильно помню), но я считаю, что takewhile намного проще понять, поскольку он делает только 1 вещь. с iter читатель должен признать, что вы используете форму с 2 аргументами, а затем он должен помнить, чем она отличается от формы с 1 аргументом и т. д. и т. д. Конечно, вам нужно ввести немного больше — и импортировать itertools, но я считаю, что это небольшая цена за дополнительную ясность. - person mgilson; 29.03.2013
comment
К сожалению, это домашнее задание, и они специально просили использовать стандартный ввод и вывод, чтобы упростить их массовое тестирование. - person Jakemmarsh; 29.03.2013
comment
@Jakemmarsh - вы можете столкнуться с проблемами с буферизацией строк - person mgilson; 29.03.2013
comment
@JBernardo - также добавлен пример iter. - person mgilson; 29.03.2013
comment
@Jakemmarsh: raw_input читает со стандартного ввода. Если на то пошло, версия takewhile явно использует stdin так же, как и ваша программа (за исключением того, что она не вызывает readlines(), что является проблемой с вашим кодом). Итак… что заставляет вас думать, что это нарушает требование профессора использовать стандартный ввод? - person abarnert; 29.03.2013

В этом коде есть несколько отдельных проблем:

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 в любой момент.

Вы можете легко это исправить, добавив break после run = False.


Но, с этим исправлением или без него, если вы не набрали quit в первый раз во внешнем цикле, поскольку вы уже прочитали все введенные данные, читать больше нечего, поэтому вы просто продолжайте запускать пустой внутренний цикл снова и снова, из которого вы никогда не сможете выйти.

У вас есть цикл, который означает чтение и обработку всего ввода. Вы хотите сделать это ровно один раз. Итак, каким должен быть внешний цикл? Во всяком случае, этого не должно быть; способ сделать что-то один раз — не использовать цикл. Итак, чтобы исправить это, избавьтесь от run и цикла while run:; просто используйте внутренний цикл.


Затем, если вы наберете quit, line на самом деле будет "quit\n", потому что readlines не удаляет символы новой строки.

Вы исправите это, либо проверив "quit\n", либо stripпроверив линии.


Наконец, даже если вы устраните все эти проблемы, вам все равно придется вечно ждать, прежде чем что-либо предпринять. readlines возвращает list строк. Единственный способ сделать это — прочитать все строки, которые когда-либо будут в stdin. Вы даже не сможете начать цикл, пока не прочтете все эти строки.

Когда стандартный ввод является файлом, это происходит, когда файл заканчивается, так что это не слишком ужасно. Но когда стандартным вводом является командная строка Windows, командная строка никогда не заканчивается.* Так что это длится вечно. Вы не можете начать обработку списка строк, потому что ожидание списка строк занимает целую вечность.

Решение состоит в том, чтобы не использовать readlines(). На самом деле никогда не бывает веских причин вызывать readlines() для чего бы то ни было, stdin или нет. Все, над чем работает readlines, является уже итерируемым, заполненным строками, точно так же, как list, который дал бы вам readlines, за исключением того, что он ленив: он может давать вам строки по одной, вместо того, чтобы ждать и давая вам все сразу. (И даже если вам действительно нужен список, просто введите list(f) вместо f.readlines().)

Итак, вместо for line in sys.stdin.readlines(): просто выполните for line in sys.stdin: (или, лучше, полностью замените явный цикл и используйте последовательность преобразований итератора, как в ответе мгилсона.)


Все исправления, предложенные JBernardo, Wing Tang Wong и т. д., правильны и необходимы. Причина, по которой ни один из них не исправил ваши проблемы, заключается в том, что если у вас есть 4 ошибки и вы исправите 1, ваш код все равно не работает. Именно поэтому не работает не является полезной мерой чего-либо в программировании, и вы должны отлаживать то, что на самом деле идет не так, чтобы знать, делаете ли вы успехи.


* Я немного солгал о том, что stdin никогда не будет закончен. Если вы наберете контроль-Z (вам может понадобиться или не понадобиться следовать за ним с возвратом), то stdin завершается. Но если ваше задание состоит в том, чтобы заставить его выйти, как только пользователь введет quit‹, превратив что-то, что завершается только тогда, когда пользователь вводит quit, а затем возвращает, control-Z, return снова, вероятно, не будет считаться успешным.

person abarnert    schedule 28.03.2013