Перенаправление потоков и взаимодействие с пользователем

perenapravlenie potokov i vzaimodejstvie s polzovatelem Контекст выполнения сценариев

Выше в этом разделе мы направили вывод сценария teststreams.py на вход стандартной программы командной строки more с помощью следующей команды:

C:\\PP4E\System\Streams> python teststreams.py < input.txt | more

Но поскольку в предыдущей главе мы уже написали на языке Python собственную утилиту «more» постраничного вывода, почему не сделать так, чтобы она тоже принимала ввод из stdin? Например, если изменить последние три строки в файле more.py, представленном в примере 2.1, на следующие:

if __name__ == ‘__main__’: # если выполняется, а не импортируется

import sys

if len(sys.argv) == 1: # вывести данные из stdin, если нет аргументов

more(sys.stdin.read())

else:

more(open(sys.argv[1]).read())

Тогда, похоже, мы сможем перенаправить стандартный вывод сценария teststreams.py на стандартный ввод more.py:

подпись: c:\...\pp4e\system\streams>python teststreams.py < input.txt | python ..\more.

py

Hello stream world

Enter a number>8 squared is 64

Enter a number>6 squared is 36

Enter a number>Bye

В целом такой прием можно использовать в сценариях на языке Python. Здесь сценарий teststreams.py снова принимает данные из файла. И, как и в предыдущем разделе, вывод одной программы отправляется по каналу на ввод другой — сценарий more.py в родительском (..) каталоге.

Однако в предыдущей команде more.py кроется малозаметная проблема. В действительности эта цепочка сработала по чистой случайности: если первый сценарий будет выводить слишком много данных и сценарию more придется спрашивать пользователя о разрешении продолжить вывод, произойдет полный отказ сценария (точнее, когда функция input возбудит исключение EOFError).

Проблема в том, что улучшенный вариант more.py использует stdin для достижения двух различных целей. Он читает ответ пользователя из stdin, вызывая input, но теперь еще и принимает из stdin основные входные данные. Когда поток stdin подключается к входному файлу или каналу, его нельзя использовать для получения ответа от пользователя, потому что он содержит входные данные. Кроме того, поскольку перенаправление потока stdin происходит еще до запуска программы, невозможно узнать, был ли он перенаправлен в командной строке.

Если потребуется принимать входные данные из stdin и использовать консоль для взаимодействия с пользователем, в сценарий нужно будет внести дополнительные изменения: нам придется отказаться от использования функции input и задействовать специальные интерфейсы для чтения ответов пользователя непосредственно с клавиатуры. В Windows такую возможность обеспечивает модуль msvcrt, входящий в состав стандартной библиотеки Python; в большинстве Unix-подобных систем достаточно будет использовать файл устройства /dev/tty.

Поскольку это достаточно редкая ситуация, мы рассмотрим полную реализацию предложенного решения. В примере 3.8 показана модифицированная версия сценария more.py для Windows, которая постранично выводит данные из стандартного потока ввода при вызове без аргументов, а кроме того, в ней используются низкоуровневые и зависящие от платформы средства взаимодействия с пользователем через клавиатуру.

Пример 3.8. PP4E\System\Streams\moreplus.py

обеспечивает постраничный вывод в stdout содержимого строки, файла или потока; если запускается как самостоятельный сценарий, обеспечивает постраничный вывод содержимого потока stdin или файла, имя которого указывается в виде аргумента командной строки; когда входные данные поступают через поток stdin, исключается возможность использовать его для получения ответов пользователя —вместо этого можно использовать платформозависимые инструменты или графический интерфейс;

import sys

def getreply()

читает клавишу, нажатую пользователем, даже если stdin перенаправлен в файл или канал

#   подпись: if sys.stdin.isatty():
return input(‘?’)
else:
if sys.platform[:3] == ‘win’ import msvcrt msvcrt.putch(b’?’) key = msvcrt.getche() msvcrt.putch(b’\n’) return key
else:
assert false, ‘platform
если stdin связан с консолью,

#   читать ответ из stdin

#   если stdin был перенаправлен,

#   его нельзя использовать для чтения

#   ответа пользователя

#   использовать инструмент консоли

#   getch(), которая не выводит символ

#   для нажатой клавиши

not supported’

подпись: #для linux: open(‘/dev/tty’).readline()[:-1]

def more(text, numlines=10):

реализует постраничный вывод содержимого строки в stdout

lines = text.splitlines()

while lines:

chunk = lines[:numlines]

lines = lines[numlines:]

for line in chunk: print(line)

if lines and getreply() not in [b’y’, b’Y’]: break

if __name__ == ‘__main__’: # если выполняется, а не импортируется

if len(sys.argv) == 1: # если нет аргументов командной строки

more(sys.stdin.read()) # вывести содержимое stdin

else:

more(open(sys.argv[1]).read()) # иначе вывести содержимое файла

Большая часть нововведений этой версии находится в функции getreply. Метод файла isatty сообщает, соединен ли stdin с консолью, — если да, функция просто считывает ответ из stdin, как и раньше. Конечно, подобная дополнительная логика необходима только в сценариях, предусматривающих возможность взаимодействия с пользователем и получения входных данных из stdin. В приложениях с графическим интерфейсом можно было бы, например, выводить диалог, реализовать обработку событий от клавиатуры в виде функций обратного вызова и так далее (знакомиться с графическими интерфейсами мы будем в главе 7).

Имея на вооружении функцию getreply, можно спокойно запускать утилиту moreplus различными способами. Как и прежде, можно импортировать и непосредственно вызывать функцию этого модуля, передавая ей ту строку, которую требуется вывести постранично:

>>> from moreplus import more

>>> more(open(‘adderSmall.py’).read())

import sys

print(sum(int(line) for line in sys.stdin))

И так же, как и прежде, при запуске с аргументом командной строки этот сценарий интерактивно будет пролистывать текст указанного файла:

C:\\PP4E\System\Streams> python moreplus.py adderSmall.py import sys

print(sum(int(line) for line in sys.stdin))

C:\\PP4E\System\Streams> python moreplus.py moreplus.py

обеспечивает постраничный вывод в stdout содержимого строки, файла или потока; если запускается как самостоятельный сценарий, обеспечивает постраничный вывод содержимого потока stdin или файла, имя которого указывается в виде аргумента командной строки; когда входные данные поступают через поток stdin, исключается возможность использовать его для получения ответов пользователя — вместо этого можно использовать платформозависимые инструменты или графический интерфейс;

import sys

def getreply():

?n

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

C:\\PP4E\System\Streams> python moreplus.py < moreplus.py

обеспечивает постраничный вывод в stdout содержимого строки, файла или потока; если запускается как самостоятельный сценарий, обеспечивает постраничный вывод содержимого потока stdin или файла, имя которого указывается в виде аргумента командной строки; когда входные данные поступают через поток stdin, исключается возможность использовать его для получения ответов пользователя — вместо этого можно использовать платформозависимые инструменты или графический интерфейс;

import sys

def getreply(): ?n

C:\\PP4E\System\Streams> type moreplus.py | python moreplus.py

обеспечивает постраничный вывод в stdout содержимого строки, файла или потока; если запускается как самостоятельный сценарий, обеспечивает постраничный вывод содержимого потока stdin или файла, имя которого указывается в виде аргумента командной строки; когда входные данные поступают через поток stdin, исключается возможность использовать его для получения ответов пользователя — вместо этого можно использовать платформозависимые инструменты или графический интерфейс;

import sys

def getreply():

?n

Наконец, если вывод одного сценария отправляется по каналу на ввод другого, все работает как надо, и при этом взаимодействие с пользователем не вызывает нарушений (и вовсе не потому, что нам просто повезло):

…. \System\Streams> python teststreams.py < input.txt | python moreplus.py Hello stream world

Enter a number>8 squared is 64

Enter a number>6 squared is 36

Enter a number>Bye

Здесь стандартный вывод одного сценария подается на стандартный ввод другого сценария, находящегося в том же каталоге: moreplus.py читает вывод teststreams.py.

Все перенаправления в таких командах действуют только потому, что сценариям безразлично, чем в действительности являются стандартный ввод и вывод — консолью, файлами или каналами между программами. Например, при запуске moreplus.py как самостоятельного сценария он просто читает поток sys.stdin; командная оболочка (например, DOS в Windows, csh в Linux) прикрепляет такие потоки к источникам, определяемым командой, перед запуском сценария. Для доступа к этим источникам сценарии используют заранее открытые объекты файлов stdin и stdout, независимо от их истинной природы.

Для читателей, ведущих подсчет: мы запускали один сценарий постраничного вывода more четырьмя различными способами — импортируя и вызывая его функцию, передавая имя файла через аргумент командной строки, перенаправляя stdin в файл и передавая вывод команды по каналу в stdin. Благодаря возможности импортировать функции, принимать аргументы командной строки и получать ввод через стандартные потоки системные инструменты Python можно повторно использовать в разнообразных режимах.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий