Выбрать главу

Если вам уже приходилось изучать язык Python, вы, вероятно, знаете, что подобная совместимость по интерфейсам обычно называется полиморфизмом - неважно, чем является объект, и неважно, что делают его методы, - важно, чтобы этот объект предоставлял ожидаемый интерфейс. Такое либеральное отношение к типам данных объясняет значительную долю гибкости и выразительности программного кода на языке Python. Ниже демонстрируется способ, позволяющий сценариям переопределять собственные потоки ввода-вывода. В примере 3.9 приводится вспомогательный модуль, демонстрирующий эту идею.

Пример 3.9. PP4E\System\Streams\redirect.py

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

import sys    # импортировать встроенный модуль

class Output:    # имитирует выходной файл

def __init__(self):

self.text = ‘’    # при создании строка пустая

def write(self, string):    # добавляет строку байтов

self.text += string

def writelines(self, lines):    # добавляет все строки в список

for line in lines: self.write(line)

class Input:    # имитирует входной файл

def __init__(self, input=’’):    # аргумент по умолчанию

self.text = input    # сохранить строку при создании

def read(self, size=None):    #    необязательный    аргумент

if size == None:    #    прочитать N байт или все

res, self.text = self.text, ‘’ else:

res, self.text = self.text[:size], self.text[size:] return res def readline(self):

eoln = self.text.find(‘\n’)    #    найти смещение    следующего eoln

if eoln == *1:    #    извлечь строку    до eoln

res, self.text = self.text, ‘’ else:

res, self.text = self.text[:eoln+1], self.text[eoln+1:] return res

def redirect(function, pargs, kargs, input): # перенаправляет stdin/out savestreams = sys.stdin, sys.stdout # вызывает объект функции sys.stdin = Input(input)    # возвращает текст в stdout

sys.stdout = Output() try:

result = function(*pargs, **kargs) # вызвать функцию с аргументами output = sys.stdout.text

finally:    # восстановить, независимо от

sys.stdin, sys.stdout = savestreams # того, было ли исключение return (result, output)    # вернуть результат,

# если исключения не было

В этом модуле определены два класса, маскирующиеся под настоящие файлы:

Output

Предоставляет интерфейс (он же протокол) метода записи, предполагаемый у выходных файлов, но сохраняет всю записываемую информацию в строке, хранящейся в памяти.

Input

Предоставляет интерфейс, предполагаемый у входных файлов, но возвращает входные данные по требованию, извлекая их из хранящейся в памяти строки, переданной при создании объекта.

Функция redirect в конце этого файла объединяет эти два объекта, чтобы выполнить единственную функцию, для которой стандартные потоки ввода и вывода будут перенаправлены в объекты Python. Функции, которая вызывается функцией redirect, не требуется ни знать, ни заботиться о том, что вызываемые ею функции print и input или методы stdin и stdout в действительности будут иметь дело с нашими объектами, а не с настоящим файлом, каналом или пользователем.

Чтобы продемонстрировать, как действует эта функция, импортируем и вызовем функцию interact, лежащую в основе сценария teststreams, представленного в примере 3.5, который прежде мы запускали из командной строки (для использования вспомогательной функции перенаправления нужно действовать на языке функций, а не файлов). При непосредственном вызове функция читает данные с клавиатуры и выводит результаты на экран, как если бы она выполнялась как программа без перенаправления:

C:\...\PP4E\System\Streams> python >>> from teststreams import interact >>> interact()

Hello stream world Enter a number>2

2    squared is 4 Enter a number>3

3    squared is 9 Enter a number^Z Bye

>>>

Теперь вызовем эту функцию под управлением функции перенаправления в redirect.py и передадим ей некоторый готовый входной текст. В этом случае на вход функции interact поступит переданная строка ('4\n5\n6\n ’ - три строки с явными символами конца строки), а результатом выполнения функции будет кортеж, содержащий возвращаемое значение и строку с текстом, который был записан в стандартный поток вывода:

>>> from redirect import redirect

>>> (result, output) = redirect(interact, (), {}, '4\n5\n6\n')

>>> print(result)

None

>>> output

‘Hello stream world\nEnter a number>4 squared is 16\nEnter a number>5 squared is 25\nEnter a number>6 squared is 36\nEnter a number>Bye\n’

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

>>> for line in output.splitlines(): print(line)