Множество входове и изходи в подпроцеса на python комуникират

Трябва да направя нещо като тази публикация, но трябва да създам подпроцес, който може да получава вход и да дава изход много пъти. Приетият отговор на тази публикация има добър код...

from subprocess import Popen, PIPE, STDOUT

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)    
grep_stdout = p.communicate(input=b'one\ntwo\nthree\nfour\nfive\nsix\n')[0]
print(grep_stdout.decode())

# four
# five

... че бих искал да продължа така:

grep_stdout2 = p.communicate(input=b'spam\neggs\nfrench fries\nbacon\nspam\nspam\n')[0]
print(grep_stdout2.decode())

# french fries

Но уви получавам следната грешка:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/subprocess.py", line 928, in communicate
    raise ValueError("Cannot send input after starting communication")
ValueError: Cannot send input after starting communication

Методът proc.stdin.write() не ви позволява да събирате изход, ако разбирам правилно. Кой е най-лесният начин да поддържате линиите отворени за текущ вход/изход?

Редактиране: =====================

Изглежда, че pexpect е полезна библиотека за това, което се опитвам да направя, но имам проблеми да я накарам да работи. Ето по-пълно обяснение на действителната ми задача. Използвам hfst, за да получа граматически анализи на отделни (руски) думи. Следното демонстрира поведението му в bash shell:

$ hfst-lookup analyser-gt-desc.hfstol
> слово
слово   слово+N+Neu+Inan+Sg+Acc 0.000000
слово   слово+N+Neu+Inan+Sg+Nom 0.000000

> сработай
сработай    сработать+V+Perf+IV+Imp+Sg2 0.000000
сработай    сработать+V+Perf+TV+Imp+Sg2 0.000000

> 

Искам моят скрипт да може да получава анализите на една форма наведнъж. Опитах код като този, но не работи.

import pexpect

analyzer = pexpect.spawnu('hfst-lookup analyser-gt-desc.hfstol')
for newWord in ['слово','сработай'] :
    print('Trying', newWord, '...')
    analyzer.expect('> ')
    analyzer.sendline( newWord )
    print(analyzer.before)

# trying слово ...
# 
# trying сработай ...
# слово
# слово слово+N+Neu+Inan+Sg+Acc 0.000000
# слово слово+N+Neu+Inan+Sg+Nom 0.000000
# 
# 

Очевидно не съм разбрал какво прави pexpect.before. Как мога да получа резултата за всяка дума, една по една?


person reynoldsnlp    schedule 19.02.2015    source източник
comment
Методът proc.stdin.write() не ви позволява да събирате изход, все още можете да получите изход, просто трябва да го получите от proc.stdout и proc.stderr.   -  person Joshua Taylor    schedule 19.02.2015
comment
Това windows или linux ли е? В linux модулът pexpect е добър избор за взаимодействие на подпроцеси.   -  person tdelaney    schedule 19.02.2015
comment
Какво се опитваш да направиш?   -  person Padraic Cunningham    schedule 19.02.2015
comment
задължително четене, ако искате множество входове и изходи: В: Защо просто не използвате канал (popen())?   -  person jfs    schedule 20.02.2015
comment
@PadraicCunningham Опитвам се да получа пример за доказателство за концепция, който мога да разширя, за да използвам в скрипт, който ще трябва да взаимодейства с моя процес 1 000 000+ пъти. Текущият ми скрипт използва subprocess.check_output, което е твърде бавно, тъй като трябва да инициира процеса за всяко взаимодействие.   -  person reynoldsnlp    schedule 20.02.2015
comment
@tdelaney Това е на OS X.   -  person reynoldsnlp    schedule 20.02.2015
comment
последователността трябва да бъде: 0. изчакайте първата подкана 1. изпратете дума 2. изчакайте подканата, получете отговор (.after?) за думата 3. повторете 1-2   -  person jfs    schedule 20.02.2015


Отговори (4)


Popen.communicate() е помощен метод, който извършва еднократно записване на данни в stdin и създава нишки за изтегляне на данни от stdout и stderr. Той затваря stdin, когато приключи записването на данни и чете stdout и stderr, докато тези канали се затворят. Не можете да направите второ communicate, защото детето вече е излязло, докато се върне.

Интерактивна сесия с дъщерен процес е доста по-сложна.

Един проблем е дали дъщерният процес изобщо разпознава, че трябва да бъде интерактивен. В C библиотеките, които повечето програми от командния ред използват за взаимодействие, програмите, изпълнявани от терминали (напр. Linux конзола или "pty" псевдо-терминал) са интерактивни и изчистват изхода си често, но тези, които се изпълняват от други програми чрез PIPES, не са интерактивни и изчистват изхода си рядко.

Друг е как трябва да четете и обработвате stdout и stderr без блокиране. Например, ако блокирате четенето на stdout, но stderr запълни канала си, детето ще спре и вие сте блокирани. Можете да използвате нишки, за да изтеглите и двете във вътрешни буфери.

Друго е как се справяте с дете, което излиза неочаквано.

За "unixy" системи като linux и OSX, модулът pexpect е написан, за да се справи със сложността на интерактивен дъщерен процес. За Windows няма добър инструмент, който да познавам, за да го направя.

person tdelaney    schedule 19.02.2015
comment
можете да блокирате дори само със stdin/stdout, например как да разберете кога да четете от stdout на grep, след като сте написали нещо? Също така дъщерният процес може напълно да заобиколи stdin/stdout (типичен пример: подкана за парола). Вижте връзката в коментара по-горе. В много случаи тези проблеми могат да бъдат разрешени с помощта на нишки, fcntl, async.io: select/poll/epoll/kqueue/iocp и/или pty. И понякога е достатъчно да внимавате - person jfs; 20.02.2015
comment
@J.F.Sebastian - и след това има програми, които четат типа на терминала, за да оцветят или извеждат на цял екран. Може да е предизвикателство. - person tdelaney; 20.02.2015
comment
ако програмата не предоставя опции за отмяна на такова поведение (като --color); може да се счита за грешка. Поведението по подразбиране трябва да е за интерактивен потребител – по-малко писане, кратък изход. Не трябва да се използва цял екран, освен ако не е абсолютно необходимо. - person jfs; 20.02.2015

Този отговор трябва да се припише на @J.F.Sebastian. Благодаря за мненията!

Следният код получи очакваното от мен поведение:

import pexpect

analyzer = pexpect.spawn('hfst-lookup analyser-gt-desc.hfstol', encoding='utf-8')
analyzer.expect('> ')

for word in ['слово', 'сработай']:
    print('Trying', word, '...')
    analyzer.sendline(word)
    analyzer.expect('> ')
    print(analyzer.before)
person reynoldsnlp    schedule 24.02.2015
comment
@Mooncrater изглежда, че pexpect.spawnu е отхвърлен в полза на използването на spawn(encoding='utf-8'). Актуализирах отговора съответно. Той обаче все още е в изходния код (github. com/pexpect/pexpect/blob/master/pexpect/), така че се чудя дали сте инсталирали pexpect правилно. - person reynoldsnlp; 28.07.2020
comment
Очаквайте, ако е добре да работите, за да симулирате взаимодействие с терминал, за програми, които ще променят поведението си, когато са в конвейер. Въпреки това, не е добре, ако комуникирате с двоични данни. - person Camion; 15.07.2021
comment
@Camion за двоични данни, просто пропуснете аргумента encoding='utf-8'. По подразбиране spawn очаква двоичен код. - person reynoldsnlp; 15.07.2021
comment
Не мисля, че е толкова просто поради обработката на новите линии. Не го проверих в момента, но вярвам, че дори и без кодирането pexpect ще преобразува знаците в края на реда в прозорци като край на ред (\r\n), и може също да имате проблеми, ако краят на вашите данни не е нов линия. - person Camion; 15.07.2021

Всеки път, когато искате да изпратите входни данни към процеса, използвайте proc.stdin.write(). Всеки път, когато искате да получите резултат от процеса, използвайте proc.stdout.read(). И двата аргумента stdin и stdout на конструктора трябва да бъдат зададени на PIPE.

person Tom Hunt    schedule 19.02.2015
comment
Работи перфектно. Процесът приема един вход и изпраща един изход. - person dom free; 23.03.2018
comment
Това трябва да се използва с повишено внимание с разклонители на линии и промиване на PIPE, тъй като може да създаде блокирания. Повече може да се намери в тази страхотна публикация в блог: eli.thegreenplace.net/2017/ - person Maged Saeed; 14.05.2020

HFST има обвързвания на Python: https://pypi.python.org/pypi/hfst

Използването им трябва да избегне целия проблем с промиването и ще ви даде по-чист API, с който да работите, отколкото анализирането на изходния низ от pexpect.

От Python REPL можете да получите някои документи за свързванията с

dir(hfst)
help(hfst.HfstTransducer)

или прочетете https://hfst.github.io/python/3.12.2/QuickStart.html

Извличане на съответните части от документите:

istr = hfst.HfstInputStream('hfst-lookup analyser-gt-desc.hfstol')
transducers = []
while not (istr.is_eof()):
    transducers.append(istr.read())
istr.close()
print("Read %i transducers in total." % len(transducers))
if len(transducers) == 1:
  out = transducers[0].lookup_optimize("слово")
  print("got %s" % (out,))
else: 
  pass # or handle >1 fst in the file, though I'm guessing you don't use that feature
person unhammer    schedule 22.11.2017